Skip to content

Instantly share code, notes, and snippets.

@timku
Created August 4, 2025 07:51
Show Gist options
  • Select an option

  • Save timku/fee1b7139289928f321bd093d6a1b044 to your computer and use it in GitHub Desktop.

Select an option

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
<?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