mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-11-15 21:00:14 +00:00
[Web] Add archive module classes
This commit is contained in:
17 files changed
+1685
No files matched your search
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Repository;
|
||||
|
||||
use Mailcow\Archive\Model\Mailbox;
|
||||
use Mailcow\Core\Repository;
|
||||
|
||||
/**
|
||||
* Read-only lookups of the address tables, with the same conditions as the Postfix maps
|
||||
* (data/Dockerfiles/postfix/postfix.sh), and the mailboxes with their archive settings.
|
||||
*/
|
||||
class LookupRepository extends Repository
|
||||
{
|
||||
/** Columns of a mailbox that receives mail; the domain must be active and no backup MX */
|
||||
private const MAILBOX = "SELECT m.`username`, m.`domain`, m.`name`, m.`active`,
|
||||
JSON_VALUE(m.`attributes`, '$.archive') AS `archive`, d.`archive` AS `domain_archive`
|
||||
FROM `mailbox` m
|
||||
JOIN `domain` d ON d.`domain` = m.`domain`
|
||||
WHERE m.`kind` = '' AND (m.`active` = '1' OR m.`active` = '2') AND d.`active` = '1' AND d.`backupmx` = '0'";
|
||||
|
||||
/** recipient_canonical_maps: new destination of an address or '@domain' */
|
||||
public function recipientMap(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `new_dest` FROM `recipient_maps` WHERE `old_dest` = :key AND `active` = '1' LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Targets of an alias row (aliases, catch-alls '@domain', groups, mailbox self rows) */
|
||||
public function aliasGoto(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `goto` FROM `alias` WHERE `address` = :key AND (`active` = '1' OR `active` = '2') LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Resources (location, thing, group) are discarded by Postfix */
|
||||
public function isResource(string $username): bool
|
||||
{
|
||||
return $this->value("SELECT `username` FROM `mailbox` WHERE `username` = :key AND `kind` REGEXP 'location|thing|group' LIMIT 1", $username) !== null;
|
||||
}
|
||||
|
||||
public function spamAliasGoto(string $key): ?string
|
||||
{
|
||||
return $this->value("SELECT `goto` FROM `spamalias` WHERE `address` = :key
|
||||
AND (`validity` >= UNIX_TIMESTAMP() OR `permanent` != 0) LIMIT 1", $key);
|
||||
}
|
||||
|
||||
/** Mailbox local@target behind local@domain when domain is an alias domain */
|
||||
public function aliasDomainMailbox(string $local, string $domain): ?string
|
||||
{
|
||||
$username = $this->query("SELECT `mailbox`.`username` FROM `mailbox`, `alias_domain`
|
||||
WHERE `alias_domain`.`alias_domain` = :domain AND `mailbox`.`username` = CONCAT(:local, '@', `alias_domain`.`target_domain`)
|
||||
AND (`mailbox`.`active` = '1' OR `mailbox`.`active` = '2') AND `alias_domain`.`active` = '1' LIMIT 1",
|
||||
['domain' => $domain, 'local' => $local])->fetchColumn();
|
||||
return is_string($username) ? $username : null;
|
||||
}
|
||||
|
||||
/** BCC destination of an address or '@domain' as rspamd's bcc.php finds it; $type 'rcpt' or 'sender' */
|
||||
public function bccDestination(string $key, string $type): ?string
|
||||
{
|
||||
$dest = $this->query("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = :type AND `local_dest` = :key AND `active` = '1' LIMIT 1",
|
||||
['type' => $type, 'key' => $key])->fetchColumn();
|
||||
return is_string($dest) ? $dest : null;
|
||||
}
|
||||
|
||||
/** A domain or alias domain of this mailcow */
|
||||
public function isMailcowDomain(string $domain): bool
|
||||
{
|
||||
return $this->query("SELECT `domain` FROM `domain` WHERE `domain` = :domain
|
||||
UNION SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :alias LIMIT 1",
|
||||
['domain' => $domain, 'alias' => $domain])->fetchColumn() !== false;
|
||||
}
|
||||
|
||||
/** The mailbox when it receives mail */
|
||||
public function mailbox(string $username): ?Mailbox
|
||||
{
|
||||
$row = $this->query(self::MAILBOX . " AND m.`username` = :username", ['username' => $username])->fetch();
|
||||
return is_array($row) ? Mailbox::fromRow($row) : null;
|
||||
}
|
||||
|
||||
/** @return Mailbox[] mailboxes archived by their setting or their domain (without the global switch) */
|
||||
public function archivedMailboxes(): array
|
||||
{
|
||||
$rows = $this->query(self::MAILBOX . " AND (JSON_VALUE(m.`attributes`, '$.archive') = '1'
|
||||
OR (COALESCE(JSON_VALUE(m.`attributes`, '$.archive'), '') NOT IN ('0', '1') AND d.`archive` = '1'))
|
||||
ORDER BY m.`username`")->fetchAll();
|
||||
return array_map(fn(array $row): Mailbox => Mailbox::fromRow($row), $rows);
|
||||
}
|
||||
|
||||
private function value(string $sql, string $key): ?string
|
||||
{
|
||||
$value = $this->query($sql, ['key' => $key])->fetchColumn();
|
||||
return is_string($value) ? $value : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mailcow\Archive\Repository;
|
||||
|
||||
use Mailcow\Archive\Model\Settings;
|
||||
use Mailcow\Core\Repository;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* SQL for the archive target: the settings (table `archive_settings`, name => value) and the row the Postfix maps
|
||||
* read (table `archive_transport`: transport, SASL, TLS policy of the archive_smtp service).
|
||||
*/
|
||||
class SettingsRepository extends Repository
|
||||
{
|
||||
/** Stored settings; keys that are not stored yet keep the defaults of the model */
|
||||
public function load(): Settings
|
||||
{
|
||||
$rows = $this->query("SELECT `name`, `value` FROM `archive_settings`")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$settings = new Settings();
|
||||
$settings->enabled = ($rows['enabled'] ?? '0') === '1';
|
||||
$settings->address = (string)($rows['address'] ?? '');
|
||||
$settings->host = (string)($rows['host'] ?? '');
|
||||
$settings->port = (int)($rows['port'] ?? $settings->port);
|
||||
$settings->tls = (string)($rows['tls'] ?? $settings->tls);
|
||||
$settings->username = (string)($rows['username'] ?? '');
|
||||
$settings->password = (string)($rows['password'] ?? '');
|
||||
$settings->profile = (string)($rows['profile'] ?? $settings->profile);
|
||||
$settings->signingKey = (string)($rows['signing_secret'] ?? '');
|
||||
$settings->headerFrom = (string)($rows['header_from'] ?? $settings->headerFrom);
|
||||
$settings->headerRcpt = (string)($rows['header_rcpt'] ?? $settings->headerRcpt);
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the settings and the transport row. The row stays while an address is set, also with archiving off,
|
||||
* so copies still queued in Postfix are delivered.
|
||||
*/
|
||||
public function save(Settings $settings): void
|
||||
{
|
||||
$values = [
|
||||
'enabled' => $settings->enabled ? '1' : '0',
|
||||
'address' => $settings->address,
|
||||
'host' => $settings->host,
|
||||
'port' => (string)$settings->port,
|
||||
'tls' => $settings->tls,
|
||||
'username' => $settings->username,
|
||||
'password' => $settings->password,
|
||||
'profile' => $settings->profile,
|
||||
'signing_secret' => $settings->signingKey,
|
||||
'header_from' => $settings->headerFrom,
|
||||
'header_rcpt' => $settings->headerRcpt,
|
||||
];
|
||||
$sql = "INSERT INTO `archive_settings` (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
$this->transaction(function () use ($sql, $values, $settings): void {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->query($sql, ['name' => $name, 'value' => $value]);
|
||||
}
|
||||
$this->query("DELETE FROM `archive_transport`");
|
||||
if ($settings->address !== '') {
|
||||
$this->insertRow('archive_transport', [
|
||||
'address' => $settings->address,
|
||||
'nexthop' => $settings->nexthop(),
|
||||
'username' => $settings->username,
|
||||
'password' => $settings->password,
|
||||
'tls_policy' => $settings->tls,
|
||||
'active' => 1,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user