generated from smedev/Template-for-SMEServer-Contribs-Package
Add in software files and templates
This commit is contained in:
468
root/opt/dmarc-srg/classes/Mail/MailBox.php
Normal file
468
root/opt/dmarc-srg/classes/Mail/MailBox.php
Normal file
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\MailboxException;
|
||||
|
||||
class MailBox
|
||||
{
|
||||
private $conn;
|
||||
private $server;
|
||||
private $host;
|
||||
private $mbox;
|
||||
private $name;
|
||||
private $uname;
|
||||
private $passw;
|
||||
private $delim;
|
||||
private $attrs;
|
||||
private $expunge;
|
||||
private $options;
|
||||
|
||||
public function __construct($params)
|
||||
{
|
||||
if (!is_array($params)) {
|
||||
throw new LogicException('Incorrect mailbox params');
|
||||
}
|
||||
|
||||
$this->conn = null;
|
||||
$this->uname = $params['username'];
|
||||
$this->passw = $params['password'];
|
||||
if (isset($params['name']) && is_string($params['name']) && strlen($params['name']) > 0) {
|
||||
$this->name = $params['name'];
|
||||
} else {
|
||||
$name = $this->uname;
|
||||
$pos = strpos($name, '@');
|
||||
if ($pos !== false && $pos !== 0) {
|
||||
$name = substr($name, 0, $pos);
|
||||
}
|
||||
$this->name = $name;
|
||||
}
|
||||
$this->mbox = $params['mailbox'];
|
||||
$this->host = $params['host'];
|
||||
|
||||
$flags = $params['encryption'] ?? '';
|
||||
switch ($flags) {
|
||||
case 'ssl':
|
||||
default:
|
||||
$flags = '/ssl';
|
||||
break;
|
||||
case 'none':
|
||||
$flags = '/notls';
|
||||
break;
|
||||
case 'starttls':
|
||||
$flags = '/tls';
|
||||
break;
|
||||
}
|
||||
if (isset($params['novalidate-cert']) && $params['novalidate-cert'] === true) {
|
||||
$flags .= '/novalidate-cert';
|
||||
}
|
||||
|
||||
$this->server = sprintf('{%s/imap%s}', $this->host, $flags);
|
||||
$this->expunge = false;
|
||||
$this->options = [];
|
||||
|
||||
if (isset($params['auth_exclude'])) {
|
||||
$auth_exclude = $params['auth_exclude'];
|
||||
switch (gettype($auth_exclude)) {
|
||||
case 'string':
|
||||
$auth_exclude = [ $auth_exclude ];
|
||||
break;
|
||||
case 'array':
|
||||
break;
|
||||
default:
|
||||
$auth_exclude = null;
|
||||
break;
|
||||
}
|
||||
if ($auth_exclude) {
|
||||
$this->options['DISABLE_AUTHENTICATOR'] = $auth_exclude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (extension_loaded('imap')) {
|
||||
$this->cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
public function childMailbox(string $mailbox_name)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$mb_list = imap_list(
|
||||
$this->conn,
|
||||
self::utf8ToMutf7($this->server),
|
||||
self::utf8ToMutf7($this->mbox) . $this->delim . self::utf8ToMutf7($mailbox_name)
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = false;
|
||||
}
|
||||
$this->ensureErrorLog('imap_list');
|
||||
if (!$mb_list) {
|
||||
return null;
|
||||
}
|
||||
$child = clone $this;
|
||||
$child->mbox .= $this->delim . $mailbox_name;
|
||||
$child->conn = null;
|
||||
$child->expunge = false;
|
||||
return $child;
|
||||
}
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function host()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function mailbox()
|
||||
{
|
||||
return $this->mbox;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
try {
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_status(
|
||||
$this->conn,
|
||||
self::utf8ToMutf7($this->server . $this->mbox),
|
||||
SA_MESSAGES | SA_UNSEEN
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog();
|
||||
if (!$res) {
|
||||
throw new MailboxException($error_message ?? 'Failed to get the mail box status');
|
||||
}
|
||||
|
||||
if ($this->attrs & \LATT_NOSELECT) {
|
||||
throw new MailboxException('The resource is not a mailbox');
|
||||
}
|
||||
|
||||
$this->checkRights();
|
||||
} catch (MailboxException $e) {
|
||||
return ErrorHandler::exceptionResult($e);
|
||||
}
|
||||
return [
|
||||
'error_code' => 0,
|
||||
'message' => 'Successfully',
|
||||
'status' => [
|
||||
'messages' => $res->messages,
|
||||
'unseen' => $res->unseen
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function search($criteria)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_search($this->conn, $criteria);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_search');
|
||||
if ($res === false) {
|
||||
if (!$error_message) {
|
||||
return [];
|
||||
}
|
||||
throw new MailboxException(
|
||||
'Failed to search email messages',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function sort($criteria, $search_criteria, $reverse)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_sort($this->conn, $criteria, $reverse ? 1 : 0, SE_NOPREFETCH, $search_criteria);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_sort');
|
||||
if ($res === false) {
|
||||
if (!$error_message) {
|
||||
return [];
|
||||
}
|
||||
throw new MailboxException(
|
||||
'Failed to sort email messages',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function message($number)
|
||||
{
|
||||
return new MailMessage($this->conn, $number);
|
||||
}
|
||||
|
||||
public function ensureMailbox($mailbox_name)
|
||||
{
|
||||
$mbn = self::utf8ToMutf7($mailbox_name);
|
||||
$srv = self::utf8ToMutf7($this->server);
|
||||
$mbo = self::utf8ToMutf7($this->mbox);
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$mb_list = imap_list($this->conn, $srv, $mbo . $this->delim . $mbn);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_list');
|
||||
if (empty($mb_list)) {
|
||||
if ($error_message) {
|
||||
throw new MailboxException(
|
||||
'Failed to get the list of mailboxes',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
|
||||
$new_mailbox = "{$srv}{$mbo}{$this->delim}{$mbn}";
|
||||
try {
|
||||
$res = imap_createmailbox($this->conn, $new_mailbox);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_createmailbox');
|
||||
if (!$res) {
|
||||
throw new MailboxException(
|
||||
'Failed to create a new mailbox',
|
||||
-1,
|
||||
new \ErrorException($error_message ?? 'Unknown')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
imap_subscribe($this->conn, $new_mailbox);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_subscribe');
|
||||
}
|
||||
}
|
||||
|
||||
public function moveMessage($number, $mailbox_name)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
$target = self::utf8ToMutf7($this->mbox) . $this->delim . self::utf8ToMutf7($mailbox_name);
|
||||
try {
|
||||
$res = imap_mail_move($this->conn, strval($number), $target);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_mail_move');
|
||||
if (!$res) {
|
||||
throw new MailboxException(
|
||||
'Failed to move a message',
|
||||
-1,
|
||||
new \ErrorException($error_message ?? 'Unknown')
|
||||
);
|
||||
}
|
||||
$this->expunge = true;
|
||||
}
|
||||
|
||||
public function deleteMessage($number)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
imap_delete($this->conn, strval($number));
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_delete');
|
||||
$this->expunge = true;
|
||||
}
|
||||
|
||||
public static function resetErrorStack()
|
||||
{
|
||||
imap_errors();
|
||||
imap_alerts();
|
||||
}
|
||||
|
||||
private function ensureConnection()
|
||||
{
|
||||
if (is_null($this->conn)) {
|
||||
$error_message = null;
|
||||
$srv = self::utf8ToMutf7($this->server);
|
||||
try {
|
||||
$this->conn = imap_open(
|
||||
$srv,
|
||||
$this->uname,
|
||||
$this->passw,
|
||||
OP_HALFOPEN,
|
||||
0,
|
||||
$this->options
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$this->conn = null;
|
||||
}
|
||||
if ($this->conn) {
|
||||
$mbx = self::utf8ToMutf7($this->mbox);
|
||||
try {
|
||||
$mb_list = imap_getmailboxes($this->conn, $srv, $mbx);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = null;
|
||||
}
|
||||
if ($mb_list && count($mb_list) === 1) {
|
||||
$this->delim = $mb_list[0]->delimiter ?? '/';
|
||||
$this->attrs = $mb_list[0]->attributes ?? 0;
|
||||
try {
|
||||
if (imap_reopen($this->conn, $srv . $mbx)) {
|
||||
return;
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
} else {
|
||||
$error_message = "Mailbox `{$this->mbox}` not found";
|
||||
}
|
||||
}
|
||||
if (!$error_message) {
|
||||
$error_message = imap_last_error();
|
||||
if (!$error_message) {
|
||||
$error_message = 'Cannot connect to the mail server';
|
||||
}
|
||||
}
|
||||
Core::instance()->logger()->error("IMAP error: {$error_message}");
|
||||
self::resetErrorStack();
|
||||
if ($this->conn) {
|
||||
try {
|
||||
imap_close($this->conn);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_close');
|
||||
}
|
||||
$this->conn = null;
|
||||
throw new MailboxException($error_message);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureErrorLog(string $prefix = 'IMAP error')
|
||||
{
|
||||
if ($error_message = imap_last_error()) {
|
||||
self::resetErrorStack();
|
||||
$error_message = "{$prefix}: {$error_message}";
|
||||
Core::instance()->logger()->error($error_message);
|
||||
return $error_message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function checkRights(): void
|
||||
{
|
||||
if ($this->attrs & \LATT_NOINFERIORS) {
|
||||
throw new SoftException('The mailbox may not have any children mailboxes');
|
||||
}
|
||||
|
||||
if (!function_exists('imap_getacl')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mbox = self::utf8ToMutf7($this->mbox);
|
||||
try {
|
||||
$acls = imap_getacl($this->conn, $mbox);
|
||||
} catch (\ErrorException $e) {
|
||||
// It's not possible to get the ACLs information
|
||||
$acls = false;
|
||||
}
|
||||
$this->ensureErrorLog('imap_getacl');
|
||||
if ($acls !== false) {
|
||||
$needed_rights_map = [
|
||||
'l' => 'LOOKUP',
|
||||
'r' => 'READ',
|
||||
's' => 'WRITE-SEEN',
|
||||
't' => 'WRITE-DELETE',
|
||||
'k' => 'CREATE'
|
||||
];
|
||||
$result = [];
|
||||
$needed_rights = array_keys($needed_rights_map);
|
||||
foreach ([ "#{$this->uname}", '#authenticated', '#anyone' ] as $identifier) {
|
||||
if (isset($acls[$identifier])) {
|
||||
$rights = $acls[$identifier];
|
||||
foreach ($needed_rights as $r) {
|
||||
if (!str_contains($rights, $r)) {
|
||||
$result[] = $needed_rights_map[$r];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count($result) > 0) {
|
||||
throw new SoftException(
|
||||
'Not enough rights. Additionally, these rights are required: ' . implode(', ', $result)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes messages marked for deletion, if any, and closes the connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup(): void
|
||||
{
|
||||
self::resetErrorStack();
|
||||
if (!is_null($this->conn)) {
|
||||
try {
|
||||
if ($this->expunge) {
|
||||
imap_expunge($this->conn);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_expunge');
|
||||
|
||||
try {
|
||||
imap_close($this->conn);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_close');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a replacement for the standard function imap_utf8_to_mutf7
|
||||
*
|
||||
* @param string $s A UTF-8 encoded string
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private static function utf8ToMutf7(string $s)
|
||||
{
|
||||
return mb_convert_encoding($s, 'UTF7-IMAP', 'UTF-8');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user