222 lines
7.1 KiB
JavaScript
222 lines
7.1 KiB
JavaScript
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { models } = require('../db/db');
|
|
const { Mailbox, Alias, User } = models;
|
|
const { format, isBefore, subDays } = require('date-fns');
|
|
|
|
async function scanMailboxDirectories(callback) {
|
|
const mailPath = '/var/mail';
|
|
|
|
// Verify mail directory exists
|
|
try {
|
|
await fs.access(mailPath);
|
|
} catch (err) {
|
|
console.error(`Mail directory ${mailPath} does not exist or is not accessible`);
|
|
return;
|
|
}
|
|
|
|
const domains = await fs.readdir(mailPath);
|
|
console.log(`Found ${domains.length} domains in ${mailPath}`);
|
|
|
|
for (const domain of domains) {
|
|
const domainPath = path.join(mailPath, domain);
|
|
const stat = await fs.stat(domainPath);
|
|
|
|
if (stat.isDirectory()) {
|
|
const users = await fs.readdir(domainPath);
|
|
console.log(`Processing domain ${domain} with ${users.length} users`);
|
|
|
|
for (const user of users) {
|
|
const userPath = path.join(domainPath, user);
|
|
const userStat = await fs.stat(userPath);
|
|
|
|
if (userStat.isDirectory()) {
|
|
await callback({ user, domain, userPath });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function cleanupOrphanedMailboxes() {
|
|
try {
|
|
// Get all valid mailboxes from database - include ignore flag
|
|
const validMailboxes = await Mailbox.query()
|
|
.select('username', 'ignore'); // Add ignore to selection
|
|
|
|
// Create a lookup map for quick checking
|
|
const validMailboxMap = {};
|
|
validMailboxes.forEach(mailbox => {
|
|
const [user, domain] = mailbox.username.split('@');
|
|
if (!validMailboxMap[domain]) validMailboxMap[domain] = new Map();
|
|
validMailboxMap[domain].set(user, mailbox.ignore); // Store ignore status
|
|
});
|
|
|
|
await scanMailboxDirectories(async ({ user, domain, userPath }) => {
|
|
// Check if mailbox exists and is not ignored
|
|
const hasMailbox = validMailboxMap[domain] && validMailboxMap[domain].has(user);
|
|
const isIgnored = hasMailbox && validMailboxMap[domain].get(user) === 1;
|
|
|
|
// Only remove if mailbox doesn't exist or is not ignored
|
|
if (!hasMailbox || !isIgnored) {
|
|
console.log(`Checking mailbox ${user}@${domain} - exists: ${hasMailbox}, ignored: ${isIgnored}`);
|
|
console.log(`Removing orphaned mailbox: ${user}@${domain}`);
|
|
await fs.rm(userPath, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
console.log('Mailbox cleanup completed');
|
|
} catch (error) {
|
|
console.error('Error during mailbox cleanup:', error);
|
|
}
|
|
}
|
|
|
|
async function cleanupUnmatchedAndExpired() {
|
|
try {
|
|
let expired = 0;
|
|
let removed = 0;
|
|
const mailboxes = await Mailbox.query()
|
|
.select('username', 'domain', 'expires', 'ignore')
|
|
.where('ignore', 0);
|
|
|
|
const aliases = await Alias.query()
|
|
.select('address', 'domain', 'ignore')
|
|
.where('ignore', 0);
|
|
|
|
// Create lookup maps for efficient matching
|
|
const mailboxMap = new Map(
|
|
mailboxes.map(m => [m.username, m])
|
|
);
|
|
|
|
const aliasMap = new Map(
|
|
aliases.map(a => [a.address, a])
|
|
);
|
|
|
|
// Check mailboxes without matching aliases
|
|
for (const [mailboxAddress, mailbox] of mailboxMap) {
|
|
if (!aliasMap.has(mailboxAddress)) {
|
|
console.log(`Removing mailbox without alias: ${mailboxAddress}`);
|
|
await Mailbox.query()
|
|
.where('username', mailbox.username)
|
|
.patch({ active: 0 });
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
// Check aliases without matching mailboxes
|
|
for (const [aliasAddress, alias] of aliasMap) {
|
|
if (!mailboxMap.has(aliasAddress)) {
|
|
console.log(`Removing alias without mailbox: ${aliasAddress}`);
|
|
await Alias.query()
|
|
.where('address', alias.address)
|
|
.patch({ active: 0 });
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
// Check expiration for matched pairs
|
|
const currentTime = format(new Date(), 'yyyy-MM-dd HH:mm:ss');
|
|
for (const [mailboxAddress, mailbox] of mailboxMap) {
|
|
console.log(`Checking ${mailboxAddress}: expires ${mailbox.expires} vs current ${currentTime}`);
|
|
|
|
if (aliasMap.has(mailboxAddress) && isBefore(new Date(mailbox.expires), new Date())) {
|
|
console.log(`Removing expired mailbox and alias: ${mailboxAddress}`);
|
|
|
|
await Mailbox.query()
|
|
.where('username', mailbox.username)
|
|
.where('ignore', 0)
|
|
.delete();
|
|
|
|
await Alias.query()
|
|
.where('address', mailboxAddress)
|
|
.where('ignore', 0)
|
|
.delete();
|
|
|
|
expired++;
|
|
}
|
|
}
|
|
|
|
await scanMailboxDirectories(async ({ user, domain, userPath }) => {
|
|
const mailboxAddress = `${user}@${domain}`;
|
|
const mailbox = mailboxMap.get(mailboxAddress);
|
|
const alias = aliasMap.get(mailboxAddress);
|
|
|
|
// Check if this mailbox exists in database with ignore=1
|
|
const ignoredMailbox = await Mailbox.query()
|
|
.where('username', mailboxAddress)
|
|
.where('ignore', 1)
|
|
.first();
|
|
|
|
// Only remove if mailbox doesn't exist OR (exists, not ignored, and expired)
|
|
if (!ignoredMailbox && (!mailbox || !alias || (mailbox && mailbox.expires < currentTime))) {
|
|
console.log(`Removing directory for invalid/expired mailbox: ${mailboxAddress}`);
|
|
await fs.rm(userPath, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
console.log('Cleanup of unmatched and expired items completed');
|
|
} catch (error) {
|
|
console.error('Error during cleanup:', error);
|
|
}
|
|
}
|
|
|
|
async function cleanupInactiveMailboxes() {
|
|
try {
|
|
const mailboxes = await Mailbox.query()
|
|
.select('username', 'domain', 'active')
|
|
.where('active', 0)
|
|
.where('ignore', 0);
|
|
|
|
const aliases = await Alias.query()
|
|
.select('address', 'domain', 'active')
|
|
.where('active', 0)
|
|
.where('ignore', 0);
|
|
|
|
for (const mailbox of mailboxes) {
|
|
await Mailbox.query()
|
|
.where('username', mailbox.username)
|
|
.where('ignore', 0)
|
|
.delete();
|
|
}
|
|
|
|
for (const alias of aliases) {
|
|
await Alias.query()
|
|
.where('address', alias.address)
|
|
.where('ignore', 0)
|
|
.delete();
|
|
}
|
|
|
|
console.log('Cleanup of inactive mailboxes completed');
|
|
} catch (error) {
|
|
console.error('Error during cleanup:', error);
|
|
}
|
|
}
|
|
|
|
async function cleanupInactiveUsers() {
|
|
try {
|
|
const cutoffDate = format(subDays(new Date(), 30), 'yyyy-MM-dd HH:mm:ss');
|
|
|
|
const result = await User.query()
|
|
.delete()
|
|
.where('ignore', 0)
|
|
.where('last_login', '<', cutoffDate)
|
|
.returning('username');
|
|
|
|
const removed = result.length;
|
|
|
|
result.forEach(user => {
|
|
console.log(`Removing inactive user: ${user.username}`);
|
|
});
|
|
|
|
console.log(`Cleanup of inactive users completed - removed ${removed} users`);
|
|
} catch (error) {
|
|
console.error('Error during cleanup:', error);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
cleanupOrphanedMailboxes,
|
|
cleanupUnmatchedAndExpired,
|
|
cleanupInactiveMailboxes,
|
|
cleanupInactiveUsers
|
|
}; |