Created
August 4, 2025 07:51
-
-
Save timku/fee1b7139289928f321bd093d6a1b044 to your computer and use it in GitHub Desktop.
On linux read emailboxes and diplay email, also allow for deleting invidual or all emails
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * PHP Script to read an Ubuntu user's mailbox, now with added functionality: | |
| * - Lists available mailboxes in /var/mail. | |
| * - Allows switching between mailboxes. | |
| * - Adds buttons to delete individual messages or all messages. | |
| * - Allows access from any common local IP range. | |
| * | |
| * This version uses Tailwind CSS for a modern, responsive layout. | |
| * | |
| * IMPORTANT SECURITY WARNINGS: | |
| * 1. DIRECT FILE ACCESS: Accessing '/var/mail/{username}' from a web script is a | |
| * significant security risk. In a production environment, you should use | |
| * a secure method like a local IMAP server to access mailboxes. | |
| * 2. IP CHECK: The IP check is done at the application level. It is | |
| * more secure and efficient to perform this check at the web server level | |
| * before the PHP script is even executed. | |
| * 3. DANGEROUS PERMISSIONS: This script requires the web server user (e.g., 'www-data') | |
| * to have write access to the /var/mail directory and files, which is extremely | |
| * dangerous and highly discouraged. | |
| * | |
| * Run commands to get access: | |
| * sudo chown www-data:mail /var/mail/* | |
| * sudo chmod g+r /var/mail/* | |
| */ | |
| // Define the mailbox directory | |
| $mail_dir = '/var/mail'; | |
| // --- SECTION 1: ACCESS CONTROL BY IP ADDRESS --- | |
| /** | |
| * Checks if an IP address is in a private network range. | |
| * This is a more robust way to check for local IPs than simple string comparison. | |
| * @param string $ip The IP address to check. | |
| * @return bool True if the IP is local, false otherwise. | |
| */ | |
| function isLocalIp($ip) { | |
| // Check for localhost | |
| if ($ip === '127.0.0.1' || $ip === '::1') { | |
| return true; | |
| } | |
| $longIp = ip2long($ip); | |
| if ($longIp === false) { | |
| return false; | |
| } | |
| $privateIpRanges = [ | |
| // 10.0.0.0/8 | |
| [ip2long('10.0.0.0'), ip2long('10.255.255.255')], | |
| // 172.16.0.0/12 | |
| [ip2long('172.16.0.0'), ip2long('172.31.255.255')], | |
| // 192.168.0.0/16 | |
| [ip2long('192.168.0.0'), ip2long('192.168.255.255')], | |
| ]; | |
| foreach ($privateIpRanges as $range) { | |
| if ($longIp >= $range[0] && $longIp <= $range[1]) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| $clientIp = $_SERVER['REMOTE_ADDR']; | |
| if (!isLocalIp($clientIp)) { | |
| http_response_code(403); | |
| die('<div class="p-6 text-center text-red-500"><strong>Access Denied:</strong> Your IP address ' . htmlspecialchars($clientIp) . ' is not permitted.</div>'); | |
| } | |
| // --- SECTION 2: UTILITY FUNCTIONS FOR MAILBOX MANAGEMENT --- | |
| /** | |
| * Lists all available mailboxes by scanning the mail directory. | |
| * @return array A list of usernames with mailboxes. | |
| */ | |
| function listMailboxes() { | |
| global $mail_dir; | |
| $mailboxes = []; | |
| if (is_dir($mail_dir)) { | |
| $files = scandir($mail_dir); | |
| foreach ($files as $file) { | |
| if ($file !== '.' && $file !== '..' && is_file($mail_dir . '/' . $file)) { | |
| $mailboxes[] = $file; | |
| } | |
| } | |
| } | |
| return $mailboxes; | |
| } | |
| /** | |
| * Deletes a specific message from a mailbox file. | |
| * @param string $username The user's mailbox to modify. | |
| * @param int $messageIndex The 1-based index of the message to delete. | |
| * @return bool True on success, false on failure. | |
| */ | |
| function deleteMessage($username, $messageIndex) { | |
| global $mail_dir; | |
| $mailboxFile = $mail_dir . '/' . $username; | |
| if (!is_writable($mailboxFile)) { | |
| return false; | |
| } | |
| $mboxContent = file_get_contents($mailboxFile); | |
| if ($mboxContent === false) return false; | |
| // Split messages while preserving the "From " delimiter | |
| $messages = preg_split('/^(From .*)$/m', $mboxContent, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); | |
| $newContent = ''; | |
| $messageCounter = 0; | |
| for ($i = 0; $i < count($messages); $i += 2) { | |
| $messageCounter++; | |
| if ($messageCounter != $messageIndex) { | |
| $newContent .= $messages[$i] . $messages[$i + 1]; | |
| } | |
| } | |
| return file_put_contents($mailboxFile, $newContent) !== false; | |
| } | |
| /** | |
| * Deletes all messages from a mailbox file by truncating it. | |
| * @param string $username The user's mailbox to clear. | |
| * @return bool True on success, false on failure. | |
| */ | |
| function deleteAllMessages($username) { | |
| global $mail_dir; | |
| $mailboxFile = $mail_dir . '/' . $username; | |
| if (!is_writable($mailboxFile)) { | |
| return false; | |
| } | |
| // Truncate the file to delete all content | |
| return file_put_contents($mailboxFile, '') !== false; | |
| } | |
| // --- SECTION 3: HANDLE USER ACTIONS --- | |
| // Get the user from the query string, and sanitize it | |
| $mailboxes = listMailboxes(); | |
| $current_user = isset($_GET['user']) && in_array($_GET['user'], $mailboxes) ? basename($_GET['user']) : ($mailboxes[0] ?? null); | |
| $message = null; // Used for success/error messages | |
| if ($_SERVER['REQUEST_METHOD'] === 'POST') { | |
| // Check for delete all action | |
| if (isset($_POST['action']) && $_POST['action'] === 'delete_all' && isset($_POST['confirm_delete_all'])) { | |
| if (deleteAllMessages($current_user)) { | |
| $message = ['type' => 'success', 'text' => 'All messages have been deleted from ' . htmlspecialchars($current_user) . '.']; | |
| } else { | |
| $message = ['type' => 'error', 'text' => 'Error: Failed to delete all messages. Check file permissions.']; | |
| } | |
| } | |
| // Check for delete specific message action | |
| if (isset($_POST['action']) && $_POST['action'] === 'delete_message' && isset($_POST['message_index'])) { | |
| $messageIndex = (int)$_POST['message_index']; | |
| if (deleteMessage($current_user, $messageIndex)) { | |
| $message = ['type' => 'success', 'text' => 'Message #' . $messageIndex . ' has been deleted.']; | |
| } else { | |
| $message = ['type' => 'error', 'text' => 'Error: Failed to delete message #' . $messageIndex . '. Check file permissions.']; | |
| } | |
| } | |
| // Redirect with message parameters to prevent form resubmission | |
| $queryString = http_build_query(['user' => $current_user, 'message_type' => $message['type'], 'message_text' => $message['text']]); | |
| header('Location: ' . $_SERVER['PHP_SELF'] . '?' . $queryString); | |
| exit; | |
| } | |
| // Check for and display messages from the URL parameters after redirection | |
| if (isset($_GET['message_type']) && isset($_GET['message_text'])) { | |
| $message = ['type' => $_GET['message_type'], 'text' => $_GET['message_text']]; | |
| } | |
| if ($current_user === null) { | |
| die('<div class="p-6 text-center text-red-500"><strong>Error:</strong> No mailboxes found.</div>'); | |
| } | |
| $mailboxFile = $mail_dir . '/' . $current_user; | |
| // Read the mailbox content | |
| $mboxContent = file_get_contents($mailboxFile); | |
| if ($mboxContent === false) { | |
| http_response_code(500); | |
| die('<div class="p-6 text-center text-red-500"><strong>Error:</strong> Could not read mailbox file \'' . htmlspecialchars($mailboxFile) . '\'.</div>'); | |
| } | |
| // Check for write permissions and provide a clear error | |
| if (!is_writable($mailboxFile)) { | |
| http_response_code(403); | |
| $message = ['type' => 'error', 'text' => 'Error: PHP does not have write permissions for \'' . htmlspecialchars($mailboxFile) . '\'. Deletion functions are disabled.']; | |
| } | |
| // Split the content into individual messages using the mbox "From " separator. | |
| $messages = preg_split('/^(From .*)$/m', $mboxContent, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); | |
| $numMessages = count($messages) / 2; | |
| // --- SECTION 4: HTML RENDERING WITH TAILWIND CSS --- | |
| ?> | |
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Mailbox Reader</title> | |
| <!-- Tailwind CSS --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { font-family: 'Inter', sans-serif; } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 text-gray-800"> | |
| <div class="flex h-screen bg-gray-100"> | |
| <!-- Sidebar for Mailbox Selection --> | |
| <div class="w-64 bg-white shadow-lg p-6 flex-shrink-0"> | |
| <h2 class="text-2xl font-bold mb-4 text-blue-600">Mailboxes</h2> | |
| <p class="text-xs text-gray-500 mb-4">You are authorized from a local IP address: <?php echo htmlspecialchars($clientIp); ?></p> | |
| <ul class="space-y-2"> | |
| <?php foreach (listMailboxes() as $user): ?> | |
| <li> | |
| <a href="?user=<?php echo htmlspecialchars($user); ?>" class="block px-4 py-2 rounded-lg transition-colors <?php echo ($current_user === $user) ? 'bg-blue-600 text-white shadow-md font-semibold' : 'bg-gray-100 hover:bg-blue-100 text-gray-700'; ?>"> | |
| <?php echo htmlspecialchars($user); ?> | |
| </a> | |
| </li> | |
| <?php endforeach; ?> | |
| </ul> | |
| </div> | |
| <!-- Main Content Area --> | |
| <div class="flex-grow p-8 overflow-y-auto"> | |
| <div class="max-w-4xl mx-auto"> | |
| <h1 class="text-4xl font-bold mb-6 text-center text-gray-900">Mailbox for <span class="text-blue-600"><?php echo htmlspecialchars($current_user); ?></span></h1> | |
| <?php if ($message): ?> | |
| <div class="p-4 mb-6 rounded-lg text-white font-medium <?php echo $message['type'] === 'success' ? 'bg-green-500' : 'bg-red-500'; ?>"> | |
| <?php echo htmlspecialchars($message['text']); ?> | |
| </div> | |
| <?php endif; ?> | |
| <div class="bg-white shadow-lg rounded-xl p-6 mb-8"> | |
| <div class="flex justify-between items-center"> | |
| <p class="text-xl font-medium">Reading mailbox '<?php echo htmlspecialchars($mailboxFile); ?>'. Found <span class="text-blue-600 font-bold"><?php echo $numMessages; ?></span> messages.</p> | |
| <button id="deleteAllButton" class="bg-red-500 text-white font-bold py-2 px-4 rounded-full shadow-md hover:bg-red-600 transition-colors"> | |
| Delete All | |
| </button> | |
| </div> | |
| </div> | |
| <?php if ($numMessages > 0): ?> | |
| <?php for ($i = 0; $i < $numMessages; $i++): ?> | |
| <?php | |
| $messageString = trim($messages[$i * 2 + 1]); | |
| list($headers, $body) = explode("\n\n", $messageString, 2); | |
| $headerLines = explode("\n", $headers); | |
| $headerMap = []; | |
| foreach ($headerLines as $line) { | |
| if (strpos($line, ':') !== false) { | |
| list($key, $value) = explode(':', $line, 2); | |
| $headerMap[trim($key)] = trim($value); | |
| } | |
| } | |
| ?> | |
| <div class="bg-white shadow-lg rounded-xl overflow-hidden mb-6"> | |
| <div class="bg-gray-800 text-white p-4 flex justify-between items-center"> | |
| <h5 class="text-lg font-bold">Message #<?php echo $i + 1; ?></h5> | |
| <button onclick="confirmDelete(<?php echo $i + 1; ?>)" class="bg-red-500 text-white text-xs font-bold py-1 px-3 rounded-full hover:bg-red-600 transition-colors">Delete</button> | |
| </div> | |
| <div class="p-6"> | |
| <h6 class="text-xl font-semibold mb-1"><strong>Subject:</strong> <?php echo (isset($headerMap['Subject']) ? htmlspecialchars($headerMap['Subject']) : 'N/A'); ?></h6> | |
| <p class="text-gray-600 text-sm"><strong>From:</strong> <?php echo (isset($headerMap['From']) ? htmlspecialchars($headerMap['From']) : 'N/A'); ?></p> | |
| <p class="text-gray-600 text-sm"><strong>To:</strong> <?php echo (isset($headerMap['To']) ? htmlspecialchars($headerMap['To']) : 'N/A'); ?></p> | |
| <p class="text-gray-600 text-sm mb-4"><strong>Date:</strong> <?php echo (isset($headerMap['Date']) ? htmlspecialchars($headerMap['Date']) : 'N/A'); ?></p> | |
| <div class="border-t pt-4"> | |
| <p class="font-medium mb-2">Body (first 200 chars):</p> | |
| <pre class="bg-gray-100 p-4 rounded-lg text-sm text-gray-700 overflow-x-auto"><?php echo htmlspecialchars(substr($body, 0, 200)); ?>...</pre> | |
| </div> | |
| </div> | |
| </div> | |
| <?php endfor; ?> | |
| <?php else: ?> | |
| <div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded-lg relative text-center" role="alert"> | |
| <span class="block sm:inline">No messages in this mailbox.</span> | |
| </div> | |
| <?php endif; ?> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Confirmation Modals (Hidden by default) --> | |
| <div id="confirmModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50"> | |
| <div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white"> | |
| <div class="mt-3 text-center"> | |
| <h3 class="text-lg leading-6 font-medium text-gray-900">Confirm Deletion</h3> | |
| <div class="mt-2 px-7 py-3"> | |
| <p class="text-sm text-gray-500" id="confirmText"></p> | |
| </div> | |
| <div class="items-center px-4 py-3"> | |
| <form method="POST" id="deleteForm" class="inline"> | |
| <input type="hidden" name="action" id="deleteAction"> | |
| <input type="hidden" name="message_index" id="deleteIndex"> | |
| <input type="hidden" name="confirm_delete_all" value="true" id="confirmDeleteAll"> | |
| <button type="submit" class="px-4 py-2 bg-red-500 text-white text-base font-medium rounded-md w-24 shadow-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 transition-colors"> | |
| Delete | |
| </button> | |
| </form> | |
| <button id="cancelButton" class="px-4 py-2 bg-gray-200 text-gray-700 text-base font-medium rounded-md w-24 shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 transition-colors ml-4"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const confirmModal = document.getElementById('confirmModal'); | |
| const confirmText = document.getElementById('confirmText'); | |
| const deleteForm = document.getElementById('deleteForm'); | |
| const deleteAction = document.getElementById('deleteAction'); | |
| const deleteIndex = document.getElementById('deleteIndex'); | |
| const confirmDeleteAll = document.getElementById('confirmDeleteAll'); | |
| const deleteAllButton = document.getElementById('deleteAllButton'); | |
| const cancelButton = document.getElementById('cancelButton'); | |
| function showModal(text) { | |
| confirmText.textContent = text; | |
| confirmModal.classList.remove('hidden'); | |
| } | |
| function hideModal() { | |
| confirmModal.classList.add('hidden'); | |
| deleteAction.value = ''; | |
| deleteIndex.value = ''; | |
| confirmDeleteAll.disabled = true; | |
| } | |
| function confirmDelete(index) { | |
| showModal('Are you sure you want to delete message #' + index + '? This action cannot be undone.'); | |
| deleteAction.value = 'delete_message'; | |
| deleteIndex.value = index; | |
| confirmDeleteAll.disabled = true; | |
| } | |
| deleteAllButton.addEventListener('click', () => { | |
| showModal('Are you sure you want to delete ALL messages from this mailbox? This action cannot be undone.'); | |
| deleteAction.value = 'delete_all'; | |
| deleteIndex.value = ''; | |
| confirmDeleteAll.disabled = false; | |
| }); | |
| cancelButton.addEventListener('click', () => { | |
| hideModal(); | |
| }); | |
| // Hide modal on outside click | |
| window.addEventListener('click', (event) => { | |
| if (event.target === confirmModal) { | |
| hideModal(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment