init: projet complet copymail pour Raspberry Pi
Stack: Nginx + PHP 8.2 + MariaDB + Postfix + Dovecot + Samba - install.sh: script installation automatisé pour RPi OS Bookworm - Interface web: visionneuse mails + admin (comptes, domaine, dashboard) - Chaque compte mail génère automatiquement un share Samba - Processeur cron: extraction pièces jointes via zbateson/mail-mime-parser - Zéro injection SQL (PDO + prepared statements) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
8
processor/composer.json
Normal file
8
processor/composer.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"require": {
|
||||
"zbateson/mail-mime-parser": "^2.4"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true
|
||||
}
|
||||
}
|
||||
95
processor/process_mail.php
Normal file
95
processor/process_mail.php
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* process_mail.php — Traitement des emails entrants
|
||||
* Lancé par cron toutes les minutes.
|
||||
* Pour chaque compte actif, scanne le Maildir/new/, extrait les pièces jointes
|
||||
* et les dépose dans /srv/copymail/<username>/
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/../web/includes/db.php';
|
||||
|
||||
use ZBateson\MailMimeParser\MailMimeParser;
|
||||
|
||||
$parser = new MailMimeParser();
|
||||
$accounts = db()->query('SELECT * FROM accounts WHERE active = 1')->fetchAll();
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
process_account($account, $parser);
|
||||
}
|
||||
|
||||
function process_account(array $account, MailMimeParser $parser): void {
|
||||
$domain = cfg('domain');
|
||||
$maildirNew = "/var/mail/vhosts/{$domain}/{$account['username']}/Maildir/new";
|
||||
$destDir = $account['smb_path'];
|
||||
|
||||
if (!is_dir($maildirNew)) return;
|
||||
|
||||
$files = glob($maildirNew . '/*');
|
||||
if (empty($files)) return;
|
||||
|
||||
foreach ($files as $filePath) {
|
||||
if (!is_file($filePath)) continue;
|
||||
|
||||
$fileName = basename($filePath);
|
||||
|
||||
// Vérifie si déjà traité
|
||||
$st = db()->prepare('SELECT id FROM source_file WHERE file_name = ? AND account_id = ?');
|
||||
$st->execute([$fileName, $account['id']]);
|
||||
if ($st->fetch()) {
|
||||
// Déjà traité, déplace quand même
|
||||
rename($filePath, "/var/mail/vhosts/{$domain}/{$account['username']}/Maildir/cur/{$fileName}:2,S");
|
||||
continue;
|
||||
}
|
||||
|
||||
$handle = fopen($filePath, 'r');
|
||||
$message = $parser->parse($handle, false);
|
||||
fclose($handle);
|
||||
|
||||
$subject = $message->getHeaderValue('Subject') ?? '(sans sujet)';
|
||||
$from = '';
|
||||
$fromHdr = $message->getHeader('From');
|
||||
if ($fromHdr) {
|
||||
$from = method_exists($fromHdr, 'getEmail') ? $fromHdr->getEmail() : $fromHdr->getRawValue();
|
||||
}
|
||||
|
||||
// Insère l'email source
|
||||
$st = db()->prepare(
|
||||
'INSERT INTO source_file (account_id, title, sender, file_name) VALUES (?, ?, ?, ?)'
|
||||
);
|
||||
$st->execute([$account['id'], $subject, $from, $fileName]);
|
||||
$sourceId = (int)db()->lastInsertId();
|
||||
|
||||
// Extrait les pièces jointes
|
||||
$attachments = $message->getAllAttachmentParts();
|
||||
if (!empty($attachments)) {
|
||||
if (!is_dir($destDir)) mkdir($destDir, 0750, true);
|
||||
foreach ($attachments as $att) {
|
||||
$attName = $att->getHeaderParameter('Content-Disposition', 'filename')
|
||||
?? $att->getHeaderParameter('Content-Type', 'name')
|
||||
?? 'attachment_' . time();
|
||||
|
||||
// Sécurise le nom de fichier
|
||||
$attName = preg_replace('/[^a-zA-Z0-9._\- ]/', '_', $attName);
|
||||
$destPath = $destDir . '/' . $attName;
|
||||
|
||||
// Évite les collisions de nom
|
||||
if (file_exists($destPath)) {
|
||||
$ext = pathinfo($attName, PATHINFO_EXTENSION);
|
||||
$base = pathinfo($attName, PATHINFO_FILENAME);
|
||||
$destPath = $destDir . '/' . $base . '_' . time() . '.' . $ext;
|
||||
$attName = basename($destPath);
|
||||
}
|
||||
|
||||
$att->saveContent($destPath);
|
||||
|
||||
db()->prepare('INSERT INTO files (source_file_id, file_name, path) VALUES (?, ?, ?)')
|
||||
->execute([$sourceId, $attName, $destPath]);
|
||||
}
|
||||
}
|
||||
|
||||
// Déplace le mail vers cur/ (marque comme lu)
|
||||
rename($filePath, "/var/mail/vhosts/{$domain}/{$account['username']}/Maildir/cur/{$fileName}:2,S");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user