socket = $socket; $this->clientIp = $clientIp; $this->logger = $logger; $this->config = Config::getInstance(); } /** * 处理客户端数据 * @param string $data 客户端发送的数据 */ public function handle($data) { $this->logger->debug("Received data from {ip}: {data}", [ 'ip' => $this->clientIp, 'data' => rtrim($data) ]); // 按行处理数据 $lines = explode("\r\n", $data); foreach ($lines as $line) { $line = trim($line); if (empty($line)) { continue; } $this->processCommand($line); } } /** * 处理POP3命令 * @param string $command POP3命令 */ private function processCommand($command) { // 解析命令和参数 $parts = preg_split('/\s+/', $command, 2); $cmd = strtoupper($parts[0]); $params = isset($parts[1]) ? $parts[1] : ''; // 根据命令调用相应的处理方法 switch ($cmd) { case 'USER': $this->handleUser($params); break; case 'PASS': $this->handlePass($params); break; case 'STAT': $this->handleStat(); break; case 'LIST': $this->handleList($params); break; case 'RETR': $this->handleRetr($params); break; case 'DELE': $this->handleDele($params); break; case 'NOOP': $this->handleNoop(); break; case 'RSET': $this->handleRset(); break; case 'QUIT': $this->handleQuit(); break; case 'TOP': $this->handleTop($params); break; case 'UIDL': $this->handleUidl($params); break; default: $this->sendResponse(false, "Unknown command"); break; } } /** * 处理USER命令 * @param string $username 用户名 */ private function handleUser($username) { if ($this->state !== 'auth') { $this->sendResponse(false, "Bad sequence of commands"); $this->logger->warning("USER command out of sequence from {ip}", [ 'ip' => $this->clientIp ]); return; } // 验证用户名格式 if (empty($username) || strlen($username) > 50) { $this->sendResponse(false, "Invalid username"); $this->logger->warning("Invalid username format from {ip}: {username}", [ 'ip' => $this->clientIp, 'username' => $username ]); return; } $this->username = $username; $this->sendResponse(true, "User accepted"); $this->logger->info("USER command received from {ip}, username: {username}", [ 'ip' => $this->clientIp, 'username' => $this->username ]); } /** * 处理PASS命令 * @param string $password 密码 */ private function handlePass($password) { if ($this->state !== 'auth' || empty($this->username)) { $this->sendResponse(false, "Bad sequence of commands"); $this->logger->warning("PASS command out of sequence from {ip}", [ 'ip' => $this->clientIp ]); return; } $this->password = $password; try { // 调用数据库接口验证用户身份 require_once __DIR__ . '/../utils/Database.php'; $db = Database::getInstance(); // 查询用户 $sql = "SELECT * FROM user WHERE username = ? AND is_deleted = 0"; $user = $db->query($sql, [$this->username])->fetch(); if ($user && password_verify($password, $user['password'])) { $this->authenticated = true; $this->state = 'transaction'; // 初始化邮件列表(从数据库获取) $this->initMessages($user['username']); $this->sendResponse(true, "Authentication successful"); $this->logger->info("PASS command received from {ip}, authentication successful for user: {username}", [ 'ip' => $this->clientIp, 'username' => $this->username ]); } else { $this->sendResponse(false, "Invalid username or password"); $this->logger->warning("Authentication failed from {ip}, username: {username}", [ 'ip' => $this->clientIp, 'username' => $this->username ]); } } catch (Exception $e) { $this->sendResponse(false, "Authentication error"); $this->logger->error("Authentication exception from {ip}: {error}", [ 'ip' => $this->clientIp, 'error' => $e->getMessage() ]); } } /** * 处理STAT命令 */ private function handleStat() { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } $this->sendResponse(true, "{$this->messageCount} {$this->mailboxSize}"); $this->logger->info("STAT command received from {ip}", [ 'ip' => $this->clientIp ]); } /** * 处理LIST命令 * @param string $messageId 邮件ID(可选) */ private function handleList($messageId = '') { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } if (empty($messageId)) { // 列出所有邮件 $response = "{$this->messageCount} messages\r\n"; foreach ($this->messages as $id => $message) { $response .= "{$id} {$message['size']}\r\n"; } $response .= ".\r\n"; $this->sendRawResponse("+OK " . $response); } else { // 列出指定邮件 $id = (int)$messageId; if (isset($this->messages[$id])) { $this->sendResponse(true, "{$id} {$this->messages[$id]['size']}"); } else { $this->sendResponse(false, "No such message"); } } $this->logger->info("LIST command received from {ip}, messageId: {messageId}", [ 'ip' => $this->clientIp, 'messageId' => $messageId ]); } /** * 处理RETR命令 * @param string $messageId 邮件ID */ private function handleRetr($messageId) { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } $id = (int)$messageId; if (!isset($this->messages[$id])) { $this->sendResponse(false, "No such message"); return; } $message = $this->messages[$id]; $response = "{$message['size']} octets\r\n"; $response .= $message['content'] . "\r\n"; $response .= ".\r\n"; $this->sendRawResponse("+OK " . $response); $this->logger->info("RETR command received from {ip}, messageId: {messageId}", [ 'ip' => $this->clientIp, 'messageId' => $messageId ]); } /** * 处理DELE命令 * @param string $messageId 邮件ID */ private function handleDele($messageId) { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } $id = (int)$messageId; if (!isset($this->messages[$id])) { $this->sendResponse(false, "No such message"); return; } $this->deletedMessages[$id] = true; $this->sendResponse(true, "Message {$id} marked for deletion"); $this->logger->info("DELE command received from {ip}, messageId: {messageId}", [ 'ip' => $this->clientIp, 'messageId' => $messageId ]); } /** * 处理NOOP命令 */ private function handleNoop() { if (!$this->authenticated) { $this->sendResponse(false, "Not authenticated"); return; } $this->sendResponse(true, "OK"); $this->logger->info("NOOP command received from {ip}", [ 'ip' => $this->clientIp ]); } /** * 处理RSET命令 */ private function handleRset() { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } $this->deletedMessages = []; $this->sendResponse(true, "Reset completed"); $this->logger->info("RSET command received from {ip}", [ 'ip' => $this->clientIp ]); } /** * 处理QUIT命令 */ private function handleQuit() { $this->state = 'update'; $deletedCount = 0; // 删除标记的邮件 if (!empty($this->deletedMessages)) { try { require_once __DIR__ . '/../utils/Database.php'; $db = Database::getInstance(); $pdo = $db->beginTransaction(); foreach ($this->deletedMessages as $id => $value) { if (isset($this->messages[$id]) && isset($this->messages[$id]['email_id'])) { $emailId = $this->messages[$id]['email_id']; $sql = "DELETE FROM emails WHERE id = ?"; $db->execute($sql, [$emailId]); $deletedCount++; } } $db->commit($pdo); } catch (Exception $e) { $db->rollback($pdo); $this->logger->error("Failed to delete messages: {error}", [ 'error' => $e->getMessage() ]); } } $this->sendResponse(true, "Bye"); $this->logger->info("QUIT command received from {ip}, deleted {count} messages", [ 'ip' => $this->clientIp, 'count' => $deletedCount ]); // 不再直接关闭socket,而是让Pop3Server通过检测连接关闭来处理 } /** * 处理TOP命令 * @param string $params 命令参数 */ private function handleTop($params) { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } $parts = explode(' ', $params); if (count($parts) !== 2) { $this->sendResponse(false, "Invalid parameters"); return; } $id = (int)$parts[0]; $lines = (int)$parts[1]; if (!isset($this->messages[$id])) { $this->sendResponse(false, "No such message"); return; } $message = $this->messages[$id]; $headers = substr($message['content'], 0, strpos($message['content'], "\r\n\r\n")); $contentLines = explode("\r\n", substr($message['content'], strpos($message['content'], "\r\n\r\n") + 4)); $topContent = implode("\r\n", array_slice($contentLines, 0, $lines)); $response = "Top of message {$id}\r\n"; $response .= $headers . "\r\n\r\n" . $topContent . "\r\n"; $response .= ".\r\n"; $this->sendRawResponse("+OK " . $response); $this->logger->info("TOP command received from {ip}, messageId: {id}, lines: {lines}", [ 'ip' => $this->clientIp, 'id' => $id, 'lines' => $lines ]); } /** * 处理UIDL命令 * @param string $messageId 邮件ID(可选) */ private function handleUidl($messageId = '') { if (!$this->authenticated || $this->state !== 'transaction') { $this->sendResponse(false, "Not authenticated"); return; } if (empty($messageId)) { // 列出所有邮件的UID $response = "{$this->messageCount} messages\r\n"; foreach ($this->messages as $id => $message) { $response .= "{$id} {$message['uid']}\r\n"; } $response .= ".\r\n"; $this->sendRawResponse("+OK " . $response); } else { // 列出指定邮件的UID $id = (int)$messageId; if (isset($this->messages[$id])) { $this->sendResponse(true, "{$id} {$this->messages[$id]['uid']}"); } else { $this->sendResponse(false, "No such message"); } } $this->logger->info("UIDL command received from {ip}, messageId: {messageId}", [ 'ip' => $this->clientIp, 'messageId' => $messageId ]); } /** * 发送响应给客户端 * @param bool $success 是否成功 * @param string $message 响应消息 */ private function sendResponse($success, $message) { $prefix = $success ? "+OK" : "-ERR"; $response = "$prefix $message\r\n"; socket_write($this->socket, $response, strlen($response)); $this->logger->debug("Sent response to {ip}: {response}", [ 'ip' => $this->clientIp, 'response' => rtrim($response) ]); } /** * 发送原始响应给客户端 * @param string $response 响应消息 */ private function sendRawResponse($response) { socket_write($this->socket, $response, strlen($response)); $this->logger->debug("Sent raw response to {ip}: {response}", [ 'ip' => $this->clientIp, 'response' => rtrim($response) ]); } /** * 初始化邮件列表(从数据库获取) * @param string $username 用户名 */ private function initMessages($username) { try { require_once __DIR__ . '/../utils/Database.php'; $db = Database::getInstance(); // 查询用户邮件 $sql = "SELECT * FROM emails WHERE to_address = (SELECT email FROM user WHERE username = ? AND is_deleted = 0) ORDER BY send_time ASC"; $emails = $db->query($sql, [$username])->fetchAll(); $this->messages = []; $this->messageCount = 0; $this->mailboxSize = 0; $this->deletedMessages = []; if ($emails) { $id = 1; foreach ($emails as $email) { // 构建完整的邮件内容 $content = "From: {$email['from_address']}\r\n" . "To: {$email['to_address']}\r\n" . "Subject: {$email['subject']}\r\n" . "Date: {$email['created_at']}\r\n" . "\r\n" . "{$email['content']}\r\n"; $this->messages[$id] = [ 'uid' => $email['id'], 'size' => strlen($content), 'content' => $content, 'email_id' => $email['id'] // 保存数据库中的实际ID ]; $this->mailboxSize += $this->messages[$id]['size']; $id++; } } $this->messageCount = count($this->messages); $this->logger->info("Loaded {count} emails for user {username}", [ 'count' => $this->messageCount, 'username' => $this->username ]); } catch (Exception $e) { $this->messages = []; $this->messageCount = 0; $this->mailboxSize = 0; $this->deletedMessages = []; $this->logger->error("Failed to load messages for user {username}: {error}", [ 'username' => $this->username, 'error' => $e->getMessage() ]); } } }