feat(smime): decrypt incoming messages

Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
Richard Steinmetz 2023-02-14 17:28:45 +01:00
Родитель 3a5aa49ff8
Коммит 6fcb90796f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 27137D9E7D273FB2
48 изменённых файлов: 3393 добавлений и 888 удалений

Просмотреть файл

@ -12,7 +12,7 @@
- **🙈 Were not reinventing the wheel!** Based on the great [Horde](https://horde.org) libraries.
- **📬 Want to host your own mail server?** We do not have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]></description>
<version>2.3.0-alpha.4</version>
<version>2.3.0-alpha.5</version>
<licence>agpl</licence>
<author>Greta Doçi</author>
<author homepage="https://github.com/nextcloud/groupware">Nextcloud Groupware Team</author>

Просмотреть файл

@ -14,6 +14,7 @@
* @author Thomas I <thomas@oatr.be>
* @author Thomas Mueller <thomas.mueller@tmit.eu>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -34,7 +35,6 @@
namespace OCA\Mail;
use Horde_Imap_Client_Exception;
use Horde_Imap_Client_Mailbox;
use Horde_Imap_Client_Socket;
use Horde_Mail_Transport;
use Horde_Mail_Transport_Smtphorde;
@ -174,20 +174,6 @@ class Account implements JsonSerializable {
return $this->client;
}
/**
* @deprecated
* @param string $folderId
* @return Mailbox
*
* @throws ServiceException
*/
public function getMailbox($folderId) {
return new Mailbox(
$this->getImapConnection(),
new Horde_Imap_Client_Mailbox($folderId)
);
}
#[ReturnTypeWillChange]
public function jsonSerialize() {
return $this->account->toJson();

Просмотреть файл

@ -5,6 +5,7 @@
* @author Christoph Wurst <wurst.christoph@gmail.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -24,113 +25,24 @@
namespace OCA\Mail;
use Horde_Imap_Client_Data_Fetch;
use OCP\AppFramework\Db\DoesNotExistException;
use Horde_Mime_Part;
class Attachment {
/**
* @param \Horde_Imap_Client_Socket $conn
* @param \Horde_Imap_Client_Mailbox $mailBox
* @param int $messageUid
* @param string $attachmentId
*/
public function __construct($conn, $mailBox, $messageUid, $attachmentId) {
$this->conn = $conn;
$this->mailBox = $mailBox;
$this->messageUid = $messageUid;
$this->attachmentId = $attachmentId;
private Horde_Mime_Part $mimePart;
$this->load();
public function __construct(Horde_Mime_Part $mimePart) {
$this->mimePart = $mimePart;
}
/**
* @var \Horde_Imap_Client_Socket
*/
private $conn;
/**
* @var \Horde_Imap_Client_Mailbox
*/
private $mailBox;
private $messageUid;
private $attachmentId;
/**
* @var \Horde_Mime_Part
*/
private $mimePart;
private function load(): void {
$fetch_query = new \Horde_Imap_Client_Fetch_Query();
$fetch_query->bodyPart($this->attachmentId);
$fetch_query->mimeHeader($this->attachmentId);
// $list is an array of Horde_Imap_Client_Data_Fetch objects.
$ids = new \Horde_Imap_Client_Ids($this->messageUid);
$headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]);
if (!isset($headers[$this->messageUid])) {
throw new DoesNotExistException('Unable to load the attachment.');
}
/** @var Horde_Imap_Client_Data_Fetch $fetch */
$fetch = $headers[$this->messageUid];
/** @var \Horde_Mime_Headers $mimeHeaders */
$mimeHeaders = $fetch->getMimeHeader($this->attachmentId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
$this->mimePart = new \Horde_Mime_Part();
// Serve all files with a content-disposition of "attachment" to prevent Cross-Site Scripting
$this->mimePart->setDisposition('attachment');
// Extract headers from part
$contentDisposition = $mimeHeaders->getValue('content-disposition', \Horde_Mime_Headers::VALUE_PARAMS);
if (!is_null($contentDisposition) && isset($contentDisposition['filename'])) {
$this->mimePart->setDispositionParameter('filename', $contentDisposition['filename']);
} else {
$contentDisposition = $mimeHeaders->getValue('content-type', \Horde_Mime_Headers::VALUE_PARAMS);
if (isset($contentDisposition['name'])) {
$this->mimePart->setContentTypeParameter('name', $contentDisposition['name']);
}
}
/* Content transfer encoding. */
if ($tmp = $mimeHeaders->getValue('content-transfer-encoding')) {
$this->mimePart->setTransferEncoding($tmp);
}
/* Content type */
if (strstr($mimeHeaders->getValue('content-type'), 'text/calendar')) {
$this->mimePart->setType('text/calendar');
if ($this->mimePart->getContentTypeParameter('name') === null) {
$this->mimePart->setContentTypeParameter('name', 'calendar.ics');
}
} else {
// To prevent potential problems with the SOP we serve all files but calendar entries with the
// MIME type "application/octet-stream"
$this->mimePart->setType('application/octet-stream');
}
$body = $fetch->getBodyPart($this->attachmentId);
$this->mimePart->setContents($body);
}
/**
* @return string
*/
public function getContents() {
public function getContents(): string {
return $this->mimePart->getContents();
}
/**
* @return string|null
*/
public function getName() {
public function getName(): ?string {
return $this->mimePart->getName();
}
/**
* @return string
*/
public function getType() {
public function getType(): string {
return $this->mimePart->getType();
}
}

Просмотреть файл

@ -27,6 +27,7 @@ namespace OCA\Mail\Contracts;
use Horde_Imap_Client;
use Horde_Imap_Client_Socket;
use OCA\Mail\Account;
use OCA\Mail\Attachment;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\Tag;
@ -249,6 +250,15 @@ interface IMailManager {
*/
public function getMailAttachments(Account $account, Mailbox $mailbox, Message $message) : array;
/**
* @param Account $account
* @param Mailbox $mailbox
* @param Message $message
* @param string $attachmentId
* @return Attachment
*/
public function getMailAttachment(Account $account, Mailbox $mailbox, Message $message, string $attachmentId): Attachment;
/**
* @param string $imapLabel
* @param string $userId

Просмотреть файл

@ -211,12 +211,13 @@ class MessagesController extends Controller {
$client = $this->clientFactory->getClient($account);
try {
$json = $this->mailManager->getImapMessage(
$imapMessage = $this->mailManager->getImapMessage(
$client,
$account,
$mailbox,
$message->getUid(), true
)->getFullMessage($id);
);
$json = $imapMessage->getFullMessage($id);
$rawMessage = $this->mailManager->getSource(
$client,
$account,
@ -243,7 +244,13 @@ class MessagesController extends Controller {
$json['isSenderTrusted'] = $this->isSenderTrusted($message);
$smimeData = new SmimeData();
$smimeData->setIsEncrypted($message->isEncrypted() || $imapMessage->isEncrypted());
if ($imapMessage->isSigned()) {
$smimeData->setIsSigned(true);
$smimeData->setSignatureIsValid($imapMessage->isSignatureValid());
}
try {
// TODO: possibly merge with code in ImapMessageFetcher to handle verification early
$parsedMessage = Horde_Mime_Part::parseMessage($rawMessage, ['no_body' => true]);
if ($parsedMessage->getType() === 'multipart/signed') {
$smimeData->setIsSigned(true);
@ -556,15 +563,12 @@ class MessagesController extends Controller {
* @NoAdminRequired
* @NoCSRFRequired
*
* @param int $accountId
* @param string $folderId
* @param int $id
* @param string $attachmentId
*
* @return Response
*
* @throws ClientException
* @throws ServiceException
*/
#[TrapError]
public function downloadAttachment(int $id,
@ -576,8 +580,13 @@ class MessagesController extends Controller {
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$folder = $account->getMailbox($mailbox->getName());
$attachment = $folder->getAttachment($message->getUid(), $attachmentId);
$attachment = $this->mailManager->getMailAttachment(
$account,
$mailbox,
$message,
$attachmentId,
);
// Body party and embedded messages do not have a name
if ($attachment->getName() === null) {
@ -656,12 +665,22 @@ class MessagesController extends Controller {
} catch (DoesNotExistException $e) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$folder = $account->getMailbox($mailbox->getName());
if ($attachmentId === '0') {
$client = $this->clientFactory->getClient($account);
try {
$m = $this->mailManager->getImapMessage(
$client,
$account,
$mailbox,
$message->getUid(),
true // Body is required for attachments
);
} finally {
$client->logout();
}
// Save all attachments
/* @var $m IMAPMessage */
$m = $folder->getMessage($message->getUid());
$attachmentIds = array_map(static function ($a) {
return $a['id'];
}, $m->attachments);
@ -670,7 +689,12 @@ class MessagesController extends Controller {
}
foreach ($attachmentIds as $aid) {
$attachment = $folder->getAttachment($message->getUid(), $aid);
$attachment = $this->mailManager->getMailAttachment(
$account,
$mailbox,
$message,
$aid,
);
$fileName = $attachment->getName() ?? $this->l10n->t('Embedded message %s', [
$aid,

Просмотреть файл

@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
@ -82,6 +83,8 @@ use function json_encode;
* @method void setImipProcessed(bool $imipProcessed)
* @method bool isImipError()
* @method void setImipError(bool $imipError)
* @method bool|null isEncrypted()
* @method void setEncrypted(bool|null $encrypted)
*/
class Message extends Entity implements JsonSerializable {
private const MUTABLE_FLAGS = [
@ -124,6 +127,11 @@ class Message extends Entity implements JsonSerializable {
protected $imipProcessed = false;
protected $imipError = false;
/**
* @var bool|null
*/
protected $encrypted;
/** @var AddressList */
private $from;
@ -164,6 +172,7 @@ class Message extends Entity implements JsonSerializable {
$this->addType('imipMessage', 'boolean');
$this->addType('imipProcessed', 'boolean');
$this->addType('imipError', 'boolean');
$this->addType('encrypted', 'boolean');
}
/**
@ -330,6 +339,7 @@ class Message extends Entity implements JsonSerializable {
'threadRootId' => $this->getThreadRootId(),
'imipMessage' => $this->isImipMessage(),
'previewText' => $this->getPreviewText(),
'encrypted' => ($this->isEncrypted() === true),
];
}
}

Просмотреть файл

@ -490,6 +490,7 @@ class MessageMapper extends QBMapper {
->set('structure_analyzed', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
->set('updated_at', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))
->set('imip_message', $query->createParameter('imip_message'))
->set('encrypted', $query->createParameter('encrypted'))
->where($query->expr()->andX(
$query->expr()->eq('uid', $query->createParameter('uid')),
$query->expr()->eq('mailbox_id', $query->createParameter('mailbox_id'))
@ -516,6 +517,7 @@ class MessageMapper extends QBMapper {
$previewText === null ? IQueryBuilder::PARAM_NULL : IQueryBuilder::PARAM_STR
);
$query->setParameter('imip_message', $message->isImipMessage(), IQueryBuilder::PARAM_BOOL);
$query->setParameter('encrypted', $message->isEncrypted(), IQueryBuilder::PARAM_BOOL);
$query->execute();
}

Просмотреть файл

@ -81,24 +81,23 @@ class SmimeCertificateMapper extends QBMapper {
}
/**
* Find all S/MIME certificates by email address.
*
* @param string $userId
* @param string $emailAddress
* @return SmimeCertificate
* @return SmimeCertificate[]
*
* @throws DoesNotExistException
* @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception
*/
public function findByEmailAddress(string $userId, string $emailAddress): SmimeCertificate {
public function findAllByEmailAddress(string $userId, string $emailAddress): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
)
->andWhere(
$qb->expr()->eq('email_address', $qb->createNamedParameter($emailAddress, IQueryBuilder::PARAM_STR))
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId)),
$qb->expr()->eq('email_address', $qb->createNamedParameter($emailAddress)),
);
return $this->findEntity($qb);
return $this->findEntities($qb);
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* 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, or
* (at your option) any later version.
*
* 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 OCA\Mail\Exception;
use Exception;
class SmimeDecryptException extends Exception {
}

Просмотреть файл

@ -0,0 +1,513 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* 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, or
* (at your option) any later version.
*
* 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 OCA\Mail\IMAP;
use Horde_Imap_Client_Base;
use Horde_Imap_Client_Data_Envelope;
use Horde_Imap_Client_Data_Fetch;
use Horde_Imap_Client_Exception;
use Horde_Imap_Client_Exception_NoSupportExtension;
use Horde_Imap_Client_Fetch_Query;
use Horde_Imap_Client_Ids;
use Horde_Mime_Exception;
use Horde_Mime_Headers;
use Horde_Mime_Part;
use OCA\Mail\AddressList;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\Html;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Db\DoesNotExistException;
class ImapMessageFetcher {
/** @var string[] */
private array $attachmentsToIgnore = ['signature.asc', 'smime.p7s'];
private Html $htmlService;
private SmimeService $smimeService;
private string $userId;
// Conditional fetching/parsing
private bool $loadBody = false;
private int $uid;
private Horde_Imap_Client_Base $client;
private string $htmlMessage = '';
private string $plainMessage = '';
private array $attachments = [];
private array $inlineAttachments = [];
private bool $hasAnyAttachment = false;
private array $scheduling = [];
private bool $hasHtmlMessage = false;
private string $mailbox;
private string $rawReferences = '';
private string $dispositionNotificationTo = '';
public function __construct(int $uid,
string $mailbox,
Horde_Imap_Client_Base $client,
string $userId,
Html $htmlService,
SmimeService $smimeService) {
$this->uid = $uid;
$this->mailbox = $mailbox;
$this->client = $client;
$this->userId = $userId;
$this->htmlService = $htmlService;
$this->smimeService = $smimeService;
}
/**
* Configure the fetcher to fetch the body of the message.
*
* @param bool $value
* @return $this
*/
public function withBody(bool $value): ImapMessageFetcher {
$this->loadBody = $value;
return $this;
}
/**
* @param Horde_Imap_Client_Data_Fetch|null $fetch
* Will be reused if no body is requested.
* It should at least contain envelope, flags, imapDate and headerText.
* Otherwise, some data might not be parsed correctly.
* @return IMAPMessage
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws Horde_Mime_Exception
* @throws ServiceException
*/
public function fetchMessage(?Horde_Imap_Client_Data_Fetch $fetch = null): IMAPMessage {
$ids = new Horde_Imap_Client_Ids($this->uid);
$isSigned = false;
$signatureIsValid = false;
$isEncrypted = false;
if ($this->loadBody) {
// Ignore given query because lots of data needs to be fetched anyway
// TODO: reuse given query if beneficial for performance and worth the refactoring effort
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
$query->structure();
$query->flags();
$query->imapDate();
$query->headerText([
'peek' => true,
]);
$this->smimeService->addEncryptionCheckQueries($query);
$headers = $this->client->fetch($this->mailbox, $query, ['ids' => $ids]);
/** @var Horde_Imap_Client_Data_Fetch $fetch */
$fetch = $headers[$this->uid];
if (is_null($fetch)) {
throw new DoesNotExistException("This email ($this->uid) can't be found. Probably it was deleted from the server recently. Please reload.");
}
// analyse the body part
$structure = $fetch->getStructure();
$this->hasAnyAttachment = $this->hasAttachments($structure);
$isEncrypted = $this->smimeService->isEncrypted($fetch);
if ($isEncrypted) {
// Fetch and parse full text if message is encrypted in order to analyze the
// structure. Conditional fetching doesn't work for encrypted messages.
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
$query->flags();
$query->imapDate();
$query->headerText([
'peek' => true,
]);
$this->smimeService->addDecryptQueries($query, true);
$headers = $this->client->fetch($this->mailbox, $query, ['ids' => $ids]);
/** @var Horde_Imap_Client_Data_Fetch $fullTextFetch */
$fullTextFetch = $headers[$this->uid];
if (is_null($fullTextFetch)) {
throw new DoesNotExistException("This email ($this->uid) can't be found. Probably it was deleted from the server recently. Please reload.");
}
$decryptedText = $this->smimeService->decryptDataFetch($fullTextFetch, $this->userId);
$structure = Horde_Mime_Part::parseMessage($decryptedText, [
'forcemime' => true,
]);
} elseif ($structure->getType() === 'application/pkcs7-mime'
&& $structure->getContentTypeParameter('smime-type') === 'signed-data') {
// Handle smime-type="signed-data" as the content is opaque until verified
// TODO: Idea: also handle regular signed message verification here
$isSigned = true;
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
$query->flags();
$query->imapDate();
$query->headerText([
'peek' => true,
]);
$query->fullText([
'peek' => true,
]);
$headers = $this->client->fetch($this->mailbox, $query, ['ids' => $ids]);
/** @var Horde_Imap_Client_Data_Fetch $fullTextFetch */
$fullTextFetch = $headers[$this->uid];
if (is_null($fullTextFetch)) {
throw new DoesNotExistException("This email ($this->uid) can't be found. Probably it was deleted from the server recently. Please reload.");
}
$signedText = $fullTextFetch->getFullMsg();
$signatureIsValid = $this->smimeService->verifyMessage($signedText);
$signedContent = $this->smimeService->extractSignedContent($signedText);
$structure = Horde_Mime_Part::parseMessage($signedContent, [
'forcemime' => true,
]);
}
// debugging below
$structure_type = $structure->getPrimaryType();
if ($structure_type === 'multipart') {
$i = 1;
foreach ($structure->getParts() as $p) {
$this->getPart($p, (string)$i++, $isEncrypted || $isSigned);
}
} else {
$bodyPartId = $structure->findBody();
if (!is_null($bodyPartId)) {
$this->getPart($structure[$bodyPartId], $bodyPartId, $isEncrypted || $isSigned);
}
}
} elseif (is_null($fetch)) {
// Reuse given query or construct a new minimal one
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
$query->flags();
$query->imapDate();
$query->headerText([
'peek' => true,
]);
$result = $this->client->fetch($this->mailbox, $query, ['ids' => $ids]);
$fetch = $result[$this->uid];
if (is_null($fetch)) {
throw new DoesNotExistException("This email ($this->uid) can't be found. Probably it was deleted from the server recently. Please reload.");
}
}
$this->parseHeaders($fetch);
$envelope = $fetch->getEnvelope();
return new IMAPMessage(
$this->uid,
$envelope->message_id,
$fetch->getFlags(),
AddressList::fromHorde($envelope->from),
AddressList::fromHorde($envelope->to),
AddressList::fromHorde($envelope->cc),
AddressList::fromHorde($envelope->bcc),
AddressList::fromHorde($envelope->reply_to),
$this->decodeSubject($envelope),
$this->plainMessage,
$this->htmlMessage,
$this->hasHtmlMessage,
$this->attachments,
$this->inlineAttachments,
$this->hasAnyAttachment,
$this->scheduling,
$fetch->getImapDate(),
$this->rawReferences,
$this->dispositionNotificationTo,
$envelope->in_reply_to,
$isEncrypted,
$isSigned,
$signatureIsValid,
$this->htmlService, // TODO: drop the html service dependency
);
}
/**
* @param Horde_Mime_Part $p
* @param string $partNo
* @param bool $isEncrypted
* @return void
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
*/
private function getPart(Horde_Mime_Part $p, string $partNo, bool $isEncrypted): void {
// iMIP messages
// Handle text/calendar parts first because they might be attachments at the same time.
// Otherwise, some of the following if-conditions might break the handling and treat iMIP
// data like regular attachments.
$allContentTypeParameters = $p->getAllContentTypeParameters();
if ($p->getType() === 'text/calendar') {
// Handle event data like a regular attachment
// Outlook doesn't set a content disposition
// We work around this by checking for the name only
if ($p->getName() !== null) {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->uid,
'fileName' => $p->getName(),
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
}
// return if this is an event attachment only
// the method parameter determines if this is a iMIP message
if (!isset($allContentTypeParameters['method'])) {
return;
}
if (in_array(strtoupper($allContentTypeParameters['method']), ['REQUEST', 'REPLY', 'CANCEL'])) {
$this->scheduling[] = [
'id' => $p->getMimeId(),
'messageId' => $this->uid,
'method' => strtoupper($allContentTypeParameters['method']),
'contents' => $this->loadBodyData($p, $partNo, $isEncrypted),
];
return;
}
}
// Regular attachments
if ($p->isAttachment() || $p->getType() === 'message/rfc822') {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->uid,
'fileName' => $p->getName(),
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
return;
}
// Inline attachments
// Horde doesn't consider parts with content-disposition set to inline as
// attachment so we need to use another way to get them.
// We use these inline attachments to render a message's html body in $this->getHtmlBody()
$filename = $p->getName();
if ($p->getType() === 'message/rfc822' || isset($filename)) {
if (in_array($filename, $this->attachmentsToIgnore)) {
return;
}
$this->inlineAttachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->uid,
'fileName' => $filename,
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId()
];
return;
}
if ($p->getPrimaryType() === 'multipart') {
$this->handleMultiPartMessage($p, $partNo, $isEncrypted);
return;
}
if ($p->getType() === 'text/plain') {
$this->handleTextMessage($p, $partNo, $isEncrypted);
return;
}
if ($p->getType() === 'text/html') {
$this->handleHtmlMessage($p, $partNo, $isEncrypted);
return;
}
// EMBEDDED MESSAGE
// Many bounce notifications embed the original message as type 2,
// but AOL uses type 1 (multipart), which is not handled here.
// There are no PHP functions to parse embedded messages,
// so this just appends the raw source to the main message.
if ($p[0] === 'message') {
$data = $this->loadBodyData($p, $partNo, $isEncrypted);
$this->plainMessage .= trim($data) . "\n\n";
}
}
/**
* @param Horde_Mime_Part $part
* @param string $partNo
* @param bool $isFetched Body is already fetched and contained within the mime part object
* @return void
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
*/
private function handleMultiPartMessage(Horde_Mime_Part $part, string $partNo, bool $isFetched): void {
$i = 1;
foreach ($part->getParts() as $p) {
$this->getPart($p, "$partNo.$i", $isFetched);
$i++;
}
}
/**
* @param Horde_Mime_Part $p
* @param string $partNo
* @param bool $isFetched Body is already fetched and contained within the mime part object
* @return void
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
*/
private function handleTextMessage(Horde_Mime_Part $p, string $partNo, bool $isFetched): void {
$data = $this->loadBodyData($p, $partNo, $isFetched);
$this->plainMessage .= trim($data) . "\n\n";
}
/**
* @param Horde_Mime_Part $p
* @param string $partNo
* @param bool $isFetched Body is already fetched and contained within the mime part object
* @return void
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
*/
private function handleHtmlMessage(Horde_Mime_Part $p, string $partNo, bool $isFetched): void {
$this->hasHtmlMessage = true;
$data = $this->loadBodyData($p, $partNo, $isFetched);
$this->htmlMessage .= $data . "<br><br>";
}
/**
* @param Horde_Mime_Part $p
* @param string $partNo
* @param bool $isFetched Body is already fetched and contained within the mime part object
* @return string
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
*/
private function loadBodyData(Horde_Mime_Part $p, string $partNo, bool $isFetched): string {
if (!$isFetched) {
$fetch_query = new Horde_Imap_Client_Fetch_Query();
$ids = new Horde_Imap_Client_Ids($this->uid);
$fetch_query->bodyPart($partNo, [
'peek' => true
]);
$fetch_query->bodyPartSize($partNo);
$fetch_query->mimeHeader($partNo, [
'peek' => true
]);
$headers = $this->client->fetch($this->mailbox, $fetch_query, ['ids' => $ids]);
/* @var $fetch Horde_Imap_Client_Data_Fetch */
$fetch = $headers[$this->uid];
if (is_null($fetch)) {
throw new DoesNotExistException("Mail body for this mail($this->uid) could not be loaded");
}
$mimeHeaders = $fetch->getMimeHeader($partNo, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) {
$p->setTransferEncoding($enc);
}
$data = $fetch->getBodyPart($partNo);
$p->setContents($data);
}
$data = $p->getContents();
if ($data === null) {
return '';
}
// Only convert encoding if it is explicitly specified in the header because text/calendar
// data is utf-8 by default.
$charset = $p->getContentTypeParameter('charset');
if ($charset !== null && strtoupper($charset) !== 'UTF-8') {
$data = mb_convert_encoding($data, 'UTF-8', $charset);
}
return (string)$data;
}
private function hasAttachments(Horde_Mime_Part $part): bool {
foreach ($part->getParts() as $p) {
if ($p->isAttachment() || $p->getType() === 'message/rfc822') {
return true;
}
if ($this->hasAttachments($p)) {
return true;
}
}
return false;
}
private function decodeSubject(Horde_Imap_Client_Data_Envelope $envelope): string {
// Try a soft conversion first (some installations, eg: Alpine linux,
// have issues with the '//IGNORE' option)
$subject = $envelope->subject;
$utf8 = iconv('UTF-8', 'UTF-8', $subject);
if ($utf8 !== false) {
return $utf8;
}
return iconv("UTF-8", "UTF-8//IGNORE", $subject);
}
private function parseHeaders(Horde_Imap_Client_Data_Fetch $fetch): void {
/** @var resource $headersStream */
$headersStream = $fetch->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_STREAM);
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headersStream);
fclose($headersStream);
$references = $parsedHeaders->getHeader('references');
if ($references !== null) {
$this->rawReferences = $references->value_single;
}
$dispositionNotificationTo = $parsedHeaders->getHeader('disposition-notification-to');
if ($dispositionNotificationTo !== null) {
$this->dispositionNotificationTo = $dispositionNotificationTo->value_single;
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* 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, or
* (at your option) any later version.
*
* 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 OCA\Mail\IMAP;
use Horde_Imap_Client_Base;
use OCA\Mail\Service\Html;
use OCA\Mail\Service\SmimeService;
class ImapMessageFetcherFactory {
private Html $htmlService;
private SmimeService $smimeService;
public function __construct(Html $htmlService,
SmimeService $smimeService) {
$this->htmlService = $htmlService;
$this->smimeService = $smimeService;
}
public function build(int $uid,
string $mailbox,
Horde_Imap_Client_Base $client,
string $userId): ImapMessageFetcher {
return new ImapMessageFetcher(
$uid,
$mailbox,
$client,
$userId,
$this->htmlService,
$this->smimeService,
);
}
}

Просмотреть файл

@ -4,6 +4,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -27,16 +28,21 @@ use Horde_Imap_Client;
use Horde_Imap_Client_Base;
use Horde_Imap_Client_Data_Fetch;
use Horde_Imap_Client_Exception;
use Horde_Imap_Client_Exception_NoSupportExtension;
use Horde_Imap_Client_Fetch_Query;
use Horde_Imap_Client_Search_Query;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Socket;
use Horde_Mime_Exception;
use Horde_Mime_Headers;
use Horde_Mime_Mail;
use Horde_Mime_Part;
use Html2Text\Html2Text;
use OCA\Mail\Attachment;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Support\PerformanceLoggerTask;
use OCP\AppFramework\Db\DoesNotExistException;
use Psr\Log\LoggerInterface;
@ -48,15 +54,21 @@ use function in_array;
use function iterator_to_array;
use function max;
use function min;
use function reset;
use function sprintf;
class MessageMapper {
/** @var LoggerInterface */
private $logger;
public function __construct(LoggerInterface $logger) {
private SMimeService $smimeService;
private ImapMessageFetcherFactory $imapMessageFactory;
public function __construct(LoggerInterface $logger,
SmimeService $smimeService,
ImapMessageFetcherFactory $imapMessageFactory) {
$this->logger = $logger;
$this->smimeService = $smimeService;
$this->imapMessageFactory = $imapMessageFactory;
}
/**
@ -67,8 +79,9 @@ class MessageMapper {
public function find(Horde_Imap_Client_Base $client,
string $mailbox,
int $id,
string $userId,
bool $loadBody = false): IMAPMessage {
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $loadBody);
$result = $this->findByIds($client, $mailbox, new Horde_Imap_Client_Ids([$id]), $userId, $loadBody);
if (count($result) === 0) {
throw new DoesNotExistException("Message does not exist");
@ -93,7 +106,8 @@ class MessageMapper {
int $maxResults,
int $highestKnownUid,
LoggerInterface $logger,
PerformanceLoggerTask $perf): array {
PerformanceLoggerTask $perf,
string $userId): array {
/**
* To prevent memory exhaustion, we don't want to just ask for a list of
* all UIDs and limit them client-side. Instead, we can (hopefully
@ -179,7 +193,7 @@ class MessageMapper {
* there is nothing to fetch in $highestKnownUid:$upper
*/
$logger->debug("Range for findAll did not find any messages. Trying again with a succeeding range");
return $this->findAll($client, $mailbox, $maxResults, $upper, $logger, $perf);
return $this->findAll($client, $mailbox, $maxResults, $upper, $logger, $perf, $userId);
}
$uidCandidates = array_filter(
array_map(
@ -211,7 +225,8 @@ class MessageMapper {
$messages = $this->findByIds(
$client,
$mailbox,
new Horde_Imap_Client_Ids($fetchRange)
new Horde_Imap_Client_Ids($fetchRange),
$userId,
);
$perf->step('find IMAP messages by UID');
return [
@ -222,12 +237,23 @@ class MessageMapper {
}
/**
* @param Horde_Imap_Client_Base $client
* @param string $mailbox
* @param Horde_Imap_Client_Ids $ids
* @param string $userId
* @param bool $loadBody
* @return IMAPMessage[]
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws Horde_Mime_Exception
* @throws ServiceException
*/
public function findByIds(Horde_Imap_Client_Base $client,
string $mailbox,
Horde_Imap_Client_Ids $ids,
string $userId,
bool $loadBody = false): array {
$query = new Horde_Imap_Client_Fetch_Query();
$query->envelope();
@ -259,23 +285,16 @@ class MessageMapper {
$this->logger->debug("findByIds in $mailbox got " . count($ids) . " UIDs ($range) and found " . count($fetchResults) . ". minFetched=$minFetched maxFetched=$maxFetched");
}
return array_map(static function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody) {
if ($loadBody) {
return new IMAPMessage(
$client,
$mailbox,
return array_map(function (Horde_Imap_Client_Data_Fetch $fetchResult) use ($client, $mailbox, $loadBody, $userId) {
return $this->imapMessageFactory
->build(
$fetchResult->getUid(),
null,
$loadBody
);
} else {
return new IMAPMessage(
$client,
$mailbox,
$fetchResult->getUid(),
$fetchResult
);
}
$client,
$userId,
)
->withBody($loadBody)
->fetchMessage($fetchResult);
}, $fetchResults);
}
@ -422,23 +441,29 @@ class MessageMapper {
* @param Horde_Imap_Client_Socket $client
* @param string $mailbox
* @param int $uid
*
* @param string $userId
* @param bool $decrypt
* @return string|null
*
* @throws ServiceException
*/
public function getFullText(Horde_Imap_Client_Socket $client,
string $mailbox,
int $uid): ?string {
int $uid,
string $userId,
bool $decrypt = true): ?string {
$query = new Horde_Imap_Client_Fetch_Query();
$query->uid();
$query->fullText([
'peek' => true,
]);
if ($decrypt) {
$this->smimeService->addDecryptQueries($query);
} else {
$query->fullText([ 'peek' => true ]);
}
try {
$result = iterator_to_array($client->fetch($mailbox, $query, [
$result = $client->fetch($mailbox, $query, [
'ids' => new Horde_Imap_Client_Ids($uid),
]), false);
]);
} catch (Horde_Imap_Client_Exception $e) {
throw new ServiceException(
"Could not fetch message source: " . $e->getMessage(),
@ -447,23 +472,38 @@ class MessageMapper {
);
}
$msg = array_map(static function (Horde_Imap_Client_Data_Fetch $result) {
return $result->getFullMsg();
}, $result);
if (empty($msg)) {
if (($message = $result->first()) === null) {
return null;
}
return reset($msg);
if ($decrypt) {
return $this->smimeService->decryptDataFetch($message, $userId);
}
return $message->getFullMsg();
}
/**
* @param Horde_Imap_Client_Socket $client
* @param string $mailbox
* @param int $uid
* @param string $userId
* @return string|null
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws Horde_Mime_Exception
* @throws ServiceException
*/
public function getHtmlBody(Horde_Imap_Client_Socket $client,
string $mailbox,
int $uid): ?string {
string $mailbox,
int $uid,
string $userId): ?string {
$messageQuery = new Horde_Imap_Client_Fetch_Query();
$messageQuery->envelope();
$messageQuery->structure();
$this->smimeService->addEncryptionCheckQueries($messageQuery, true);
$result = $client->fetch($mailbox, $messageQuery, [
'ids' => new Horde_Imap_Client_Ids([$uid]),
@ -474,6 +514,23 @@ class MessageMapper {
}
$structure = $message->getStructure();
// Handle S/MIME encrypted message
if ($this->smimeService->isEncrypted($message)) {
// Encrypted messages have to be fully fetched in order to analyze the structure because
// it is hidden (obviously).
$fullText = $this->getFullText($client, $mailbox, $uid, $userId);
// Force mime parsing as decrypted S/MIME payload doesn't have to contain a MIME header
$mimePart = Horde_Mime_Part::parseMessage($fullText, ['forcemime' => true ]);
$htmlPartId = $mimePart->findBody('html');
if (!isset($mimePart[$htmlPartId])) {
return null;
}
return $mimePart[$htmlPartId];
}
$htmlPartId = $structure->findBody('html');
if ($htmlPartId === null) {
// No HTML part
@ -501,58 +558,31 @@ class MessageMapper {
return null;
}
/**
* @deprecated Use getAttachments() instead
*
* @param Horde_Imap_Client_Socket $client
* @param string $mailbox
* @param int $uid
* @param string $userId
* @param array|null $attachmentIds
* @return array
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws Horde_Mime_Exception
* @throws ServiceException
*/
public function getRawAttachments(Horde_Imap_Client_Socket $client,
string $mailbox,
int $uid,
?array $attachmentIds = []): array {
$messageQuery = new Horde_Imap_Client_Fetch_Query();
$messageQuery->structure();
$result = $client->fetch($mailbox, $messageQuery, [
'ids' => new Horde_Imap_Client_Ids([$uid]),
]);
if (($structureResult = $result->first()) === null) {
throw new DoesNotExistException('Message does not exist');
}
$structure = $structureResult->getStructure();
$partsQuery = $this->buildAttachmentsPartsQuery($structure, $attachmentIds);
$parts = $client->fetch($mailbox, $partsQuery, [
'ids' => new Horde_Imap_Client_Ids([$uid]),
]);
if (($messageData = $parts->first()) === null) {
throw new DoesNotExistException('Message does not exist');
}
$attachments = [];
foreach ($structure->partIterator() as $key => $part) {
/** @var Horde_Mime_Part $part */
if (!$part->isAttachment()) {
continue;
}
if (!empty($attachmentIds) && !in_array($part->getMimeId(), $attachmentIds, true)) {
// We are looking for specific parts only and this is not one of them
continue;
}
$stream = $messageData->getBodyPart($key, true);
$mimeHeaders = $messageData->getMimeHeader($key, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) {
$part->setTransferEncoding($enc);
}
$part->setContents($stream, [
'usestream' => true,
]);
$decoded = $part->getContents();
fclose($stream);
$attachments[] = $decoded;
}
return $attachments;
string $mailbox,
int $uid,
string $userId,
?array $attachmentIds = []): array {
$attachments = $this->getAttachments($client, $mailbox, $uid, $userId, $attachmentIds);
return array_map(static function (array $attachment) {
return $attachment['content'];
}, $attachments);
}
/**
@ -561,32 +591,55 @@ class MessageMapper {
* @param Horde_Imap_Client_Socket $client
* @param string $mailbox
* @param integer $uid
* @param string $userId
* @param array|null $attachmentIds
* @return array[]
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws ServiceException
* @throws Horde_Mime_Exception
*/
public function getAttachments(Horde_Imap_Client_Socket $client,
string $mailbox,
int $uid,
?array $attachmentIds = []): array {
string $mailbox,
int $uid,
string $userId,
?array $attachmentIds = []): array {
$uids = new Horde_Imap_Client_Ids([$uid]);
$messageQuery = new Horde_Imap_Client_Fetch_Query();
$messageQuery->structure();
$this->smimeService->addEncryptionCheckQueries($messageQuery);
$result = $client->fetch($mailbox, $messageQuery, [
'ids' => new Horde_Imap_Client_Ids([$uid]),
]);
$result = $client->fetch($mailbox, $messageQuery, ['ids' => $uids ]);
if (($structureResult = $result->first()) === null) {
throw new DoesNotExistException('Message does not exist');
}
$structure = $structureResult->getStructure();
$partsQuery = $this->buildAttachmentsPartsQuery($structure, $attachmentIds);
$messageData = null;
$parts = $client->fetch($mailbox, $partsQuery, [
'ids' => new Horde_Imap_Client_Ids([$uid]),
]);
if (($messageData = $parts->first()) === null) {
throw new DoesNotExistException('Message does not exist');
$isEncrypted = $this->smimeService->isEncrypted($structureResult);
if ($isEncrypted) {
$fullTextQuery = new Horde_Imap_Client_Fetch_Query();
$this->smimeService->addDecryptQueries($fullTextQuery);
$fullTextParts = $client->fetch($mailbox, $fullTextQuery, ['ids' => $uids ]);
if (($fullTextResult = $fullTextParts->first()) === null) {
throw new DoesNotExistException('Message does not exist');
}
$decryptedText = $this->smimeService->decryptDataFetch($fullTextResult, $userId);
// Replace opaque structure with decrypted structure
$structure = Horde_Mime_Part::parseMessage($decryptedText, [ 'forcemime' => true ]);
} else {
$partsQuery = $this->buildAttachmentsPartsQuery($structure, $attachmentIds);
$parts = $client->fetch($mailbox, $partsQuery, ['ids' => $uids ]);
if (($messageData = $parts->first()) === null) {
throw new DoesNotExistException('Message does not exist');
}
}
$attachments = [];
@ -602,28 +655,136 @@ class MessageMapper {
continue;
}
$stream = $messageData->getBodyPart($key, true);
$mimeHeaders = $messageData->getMimeHeader($key, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) {
$part->setTransferEncoding($enc);
if ($isEncrypted) {
// Decrypted message already contains all data
$content = $part->getContents();
} else {
$stream = $messageData->getBodyPart($key, true);
$mimeHeaders = $messageData->getMimeHeader($key, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) {
$part->setTransferEncoding($enc);
}
$part->setContents($stream, [
'usestream' => true,
]);
$content = $part->getContents();
fclose($stream);
}
$part->setContents($stream, [
'usestream' => true,
]);
$attachments[] = [
'content' => $part->getContents(),
'content' => $content,
'name' => $part->getName(),
'size' => $part->getBytes()
];
fclose($stream);
}
return $attachments;
}
/**
* @param Horde_Imap_Client_Base $client
* @param string $mailbox
* @param int $messageUid
* @param string $attachmentId
* @param string $userId
* @return Attachment
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws ServiceException
* @throws Horde_Mime_Exception
*/
public function getAttachment(Horde_Imap_Client_Base $client,
string $mailbox,
int $messageUid,
string $attachmentId,
string $userId): Attachment {
// TODO: compare logic and merge with getAttachments()
$query = new Horde_Imap_Client_Fetch_Query();
$query->bodyPart($attachmentId);
$query->mimeHeader($attachmentId);
$this->smimeService->addEncryptionCheckQueries($query);
$uids = new Horde_Imap_Client_Ids($messageUid);
$headers = $client->fetch($mailbox, $query, ['ids' => $uids]);
if (!isset($headers[$messageUid])) {
throw new DoesNotExistException('Unable to load the attachment.');
}
/** @var Horde_Imap_Client_Data_Fetch $fetch */
$fetch = $headers[$messageUid];
/** @var Horde_Mime_Headers $mimeHeaders */
$mimeHeaders = $fetch->getMimeHeader($attachmentId, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
$body = $fetch->getBodyPart($attachmentId);
$isEncrypted = $this->smimeService->isEncrypted($fetch);
if ($isEncrypted) {
$fullTextQuery = new Horde_Imap_Client_Fetch_Query();
$this->smimeService->addDecryptQueries($fullTextQuery);
$result = $client->fetch($mailbox, $fullTextQuery, ['ids' => $uids]);
if (!isset($result[$messageUid])) {
throw new DoesNotExistException('Unable to load the attachment.');
}
/** @var Horde_Imap_Client_Data_Fetch $fetch */
$fullTextResult = $result[$messageUid];
$decryptedText = $this->smimeService->decryptDataFetch($fullTextResult, $userId);
$decryptedPart = Horde_Mime_Part::parseMessage($decryptedText, [ 'forcemime' => true ]);
if (!isset($decryptedPart[$attachmentId])) {
throw new DoesNotExistException('Unable to load the attachment.');
}
$attachmentPart = $decryptedPart[$attachmentId];
$body = $attachmentPart->getContents();
$mimeHeaders = $attachmentPart->addMimeHeaders();
}
$mimePart = new Horde_Mime_Part();
// Serve all files with a content-disposition of "attachment" to prevent Cross-Site Scripting
$mimePart->setDisposition('attachment');
// Extract headers from part
$contentDisposition = $mimeHeaders->getValue('content-disposition', Horde_Mime_Headers::VALUE_PARAMS);
if (!is_null($contentDisposition) && isset($contentDisposition['filename'])) {
$mimePart->setDispositionParameter('filename', $contentDisposition['filename']);
} else {
$contentDisposition = $mimeHeaders->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS);
if (isset($contentDisposition['name'])) {
$mimePart->setContentTypeParameter('name', $contentDisposition['name']);
}
}
// Content transfer encoding
// Decrypted parts are already decoded because they went through the MIME parser
if (!$isEncrypted && $tmp = $mimeHeaders->getValue('content-transfer-encoding')) {
$mimePart->setTransferEncoding($tmp);
}
/* Content type */
$contentType = $mimeHeaders->getValue('content-type');
if (!is_null($contentType) && str_contains($contentType, 'text/calendar')) {
$mimePart->setType('text/calendar');
if ($mimePart->getContentTypeParameter('name') === null) {
$mimePart->setContentTypeParameter('name', 'calendar.ics');
}
} else {
// To prevent potential problems with the SOP we serve all files but calendar entries with the
// MIME type "application/octet-stream"
$mimePart->setType('application/octet-stream');
}
$mimePart->setContents($body);
return new Attachment($mimePart);
}
/**
* Build the parts query for attachments
*
* @param $structure
* @param Horde_Mime_Part $structure
* @param array $attachmentIds
* @return Horde_Imap_Client_Fetch_Query
*/
@ -669,17 +830,24 @@ class MessageMapper {
'cache' => true,
'peek' => true,
]);
$this->smimeService->addEncryptionCheckQueries($structureQuery);
$structures = $client->fetch($mailbox, $structureQuery, [
'ids' => new Horde_Imap_Client_Ids($uids),
]);
return array_map(static function (Horde_Imap_Client_Data_Fetch $fetchData) use ($mailbox, $client) {
return array_map(function (Horde_Imap_Client_Data_Fetch $fetchData) use ($mailbox, $client) {
$hasAttachments = false;
$text = '';
$isImipMessage = false;
$isEncrypted = false;
if ($this->smimeService->isEncrypted($fetchData)) {
$isEncrypted = true;
}
$structure = $fetchData->getStructure();
/** @var Horde_Mime_Part $part */
foreach ($structure->getParts() as $part) {
if ($part->isAttachment()) {
@ -698,7 +866,7 @@ class MessageMapper {
$textBodyId = $structure->findBody() ?? $structure->findBody('text');
$htmlBodyId = $structure->findBody('html');
if ($textBodyId === null && $htmlBodyId === null) {
return new MessageStructureData($hasAttachments, $text, $isImipMessage);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted);
}
$partsQuery = new Horde_Imap_Client_Fetch_Query();
if ($htmlBodyId !== null) {
@ -724,7 +892,7 @@ class MessageMapper {
$part = $parts[$fetchData->getUid()];
// This is sus - why does this even happen? A delete / move in the middle of this processing?
if ($part === null) {
return new MessageStructureData($hasAttachments, $text, $isImipMessage);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted);
}
$htmlBody = ($htmlBodyId !== null) ? $part->getBodyPart($htmlBodyId) : null;
if (!empty($htmlBody)) {
@ -735,7 +903,12 @@ class MessageMapper {
$htmlBody = $structure->getContents();
}
$html = new Html2Text($htmlBody);
return new MessageStructureData($hasAttachments, trim($html->getText()), $isImipMessage);
return new MessageStructureData(
$hasAttachments,
trim($html->getText()),
$isImipMessage,
$isEncrypted,
);
}
$textBody = $part->getBodyPart($textBodyId);
if (!empty($textBody)) {
@ -745,10 +918,15 @@ class MessageMapper {
$structure->setContents($textBody);
$textBody = $structure->getContents();
}
return new MessageStructureData($hasAttachments, $textBody, $isImipMessage);
return new MessageStructureData(
$hasAttachments,
$textBody,
$isImipMessage,
$isEncrypted,
);
}
return new MessageStructureData($hasAttachments, $text, $isImipMessage);
return new MessageStructureData($hasAttachments, $text, $isImipMessage, $isEncrypted);
}, iterator_to_array($structures->getIterator()));
}
}

Просмотреть файл

@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
@ -35,12 +36,16 @@ class MessageStructureData {
/** @var bool */
private $isImipMessage;
private bool $isEncrypted;
public function __construct(bool $hasAttachments,
string $previewText,
bool $isImipMessage) {
bool $isImipMessage,
bool $isEncrypted) {
$this->hasAttachments = $hasAttachments;
$this->previewText = $previewText;
$this->isImipMessage = $isImipMessage;
$this->isEncrypted = $isEncrypted;
}
public function hasAttachments(): bool {
@ -54,4 +59,8 @@ class MessageStructureData {
public function isImipMessage(): bool {
return $this->isImipMessage;
}
public function isEncrypted(): bool {
return $this->isEncrypted;
}
}

Просмотреть файл

@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
@ -110,6 +111,7 @@ class PreviewEnhancer {
$message->setPreviewText($structureData->getPreviewText());
$message->setStructureAnalyzed(true);
$message->setImipMessage($structureData->isImipMessage());
$message->setEncrypted($structureData->isEncrypted());
return $message;
}, $messages));

Просмотреть файл

@ -4,6 +4,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -65,6 +66,7 @@ class Synchronizer {
*/
public function sync(Horde_Imap_Client_Base $imapClient,
Request $request,
string $userId,
int $criteria = Horde_Imap_Client::SYNC_NEWMSGSUIDS | Horde_Imap_Client::SYNC_FLAGSUIDS | Horde_Imap_Client::SYNC_VANISHEDUIDS): Response {
$mailbox = new Horde_Imap_Client_Mailbox($request->getMailbox());
try {
@ -95,8 +97,8 @@ class Synchronizer {
throw $e;
}
$newMessages = $this->messageMapper->findByIds($imapClient, $request->getMailbox(), new Horde_Imap_Client_Ids($newUids));
$changedMessages = $this->messageMapper->findByIds($imapClient, $request->getMailbox(), new Horde_Imap_Client_Ids($changedUids));
$newMessages = $this->messageMapper->findByIds($imapClient, $request->getMailbox(), new Horde_Imap_Client_Ids($newUids), $userId);
$changedMessages = $this->messageMapper->findByIds($imapClient, $request->getMailbox(), new Horde_Imap_Client_Ids($changedUids), $userId);
$vanishedMessageUids = $vanishedUids;
return new Response($newMessages, $changedMessages, $vanishedMessageUids);

Просмотреть файл

@ -1,96 +0,0 @@
<?php
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Clement Wong <mail@clement.hk>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Lukas Reschke <lukas@owncloud.com>
* @author matiasdelellis <mati86dl@gmail.com>
* @author Robin McCorkell <rmccorkell@karoshi.org.uk>
* @author Thomas Imbreckx <zinks@iozero.be>
* @author Thomas I <thomas@oatr.be>
* @author Thomas Mueller <thomas.mueller@tmit.eu>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Maximilian Zellhofer <max.zellhofer@gmail.com>
*
* Mail
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\Mail;
use Horde_Imap_Client;
use Horde_Imap_Client_Mailbox;
use Horde_Imap_Client_Socket;
use OCA\Mail\Model\IMAPMessage;
class Mailbox {
/**
* @var Horde_Imap_Client_Socket
*/
protected $conn;
/**
* @var Horde_Imap_Client_Mailbox
*/
protected $mailBox;
/**
* @param Horde_Imap_Client_Socket $conn
* @param Horde_Imap_Client_Mailbox $mailBox
*/
public function __construct($conn, $mailBox) {
$this->conn = $conn;
$this->mailBox = $mailBox;
}
/**
* @param int $uid
* @param bool $loadHtmlMessageBody
*
* @return IMAPMessage
*/
public function getMessage(int $uid, bool $loadHtmlMessageBody = false) {
return new IMAPMessage($this->conn, $this->mailBox, $uid, null, $loadHtmlMessageBody);
}
/**
* @param int $messageUid
* @param string $attachmentId
*
* @return Attachment
*/
public function getAttachment(int $messageUid, string $attachmentId): Attachment {
return new Attachment($this->conn, $this->mailBox, $messageUid, $attachmentId);
}
/**
* @param string $rawBody
* @param array $flags
* @return array<int> UIDs
*
* @deprecated only used for testing
*/
public function saveMessage($rawBody, $flags = []) {
$uids = $this->conn->append($this->mailBox, [
[
'data' => $rawBody,
'flags' => $flags
]
])->ids;
return reset($uids);
}
}

Просмотреть файл

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Mail\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version2300Date20230214104736 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$messageTable = $schema->getTable('mail_messages');
if (!$messageTable->hasColumn('encrypted')) {
$messageTable->addColumn('encrypted', 'boolean', [
'notnull' => false,
'default' => false,
]);
}
return $schema;
}
}

Просмотреть файл

@ -32,29 +32,19 @@ namespace OCA\Mail\Model;
use Exception;
use Horde_Imap_Client;
use Horde_Imap_Client_Data_Envelope;
use Horde_Imap_Client_Data_Fetch;
use Horde_Imap_Client_DateTime;
use Horde_Imap_Client_Fetch_Query;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Mailbox;
use Horde_Imap_Client_Socket;
use Horde_Mime_Headers;
use Horde_Mime_Headers_MessageId;
use Horde_Mime_Part;
use JsonSerializable;
use OC;
use OCA\Mail\AddressList;
use OCA\Mail\Db\LocalAttachment;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\Message;
use OCA\Mail\Db\Tag;
use OCA\Mail\Service\Html;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\File;
use OCP\Files\SimpleFS\ISimpleFile;
use ReturnTypeWillChange;
use function fclose;
use function in_array;
use function mb_convert_encoding;
use function mb_strcut;
@ -63,75 +53,84 @@ use function trim;
class IMAPMessage implements IMessage, JsonSerializable {
use ConvertAddresses;
/**
* @var string[]
*/
private $attachmentsToIgnore = ['signature.asc', 'smime.p7s'];
private Html $htmlService;
/** @var Html|null */
private $htmlService;
/** @var string[] */
private array $flags;
/**
* @param Horde_Imap_Client_Socket|null $conn
* @param Horde_Imap_Client_Mailbox|string $mailBox
* @param int $messageId
* @param Horde_Imap_Client_Data_Fetch|null $fetch
* @param bool $loadHtmlMessage
* @param Html|null $htmlService
*
* @throws DoesNotExistException
*/
public function __construct($conn,
$mailBox,
int $messageId,
Horde_Imap_Client_Data_Fetch $fetch = null,
bool $loadHtmlMessage = false,
Html $htmlService = null) {
$this->conn = $conn;
$this->mailBox = $mailBox;
$this->messageId = $messageId;
$this->loadHtmlMessage = $loadHtmlMessage;
private int $messageId;
private string $realMessageId;
private AddressList $from;
private AddressList $to;
private AddressList $cc;
private AddressList $bcc;
private AddressList $replyTo;
private string $subject;
public string $plainMessage;
public string $htmlMessage;
public array $attachments;
public array $inlineAttachments;
private bool $hasAttachments;
public array $scheduling;
private bool $hasHtmlMessage;
private Horde_Imap_Client_DateTime $imapDate;
private string $rawReferences;
private string $dispositionNotificationTo;
private string $rawInReplyTo;
private bool $isEncrypted;
private bool $isSigned;
private bool $signatureIsValid;
public function __construct(int $uid,
string $messageId,
array $flags,
AddressList $from,
AddressList $to,
AddressList $cc,
AddressList $bcc,
AddressList $replyTo,
string $subject,
string $plainMessage,
string $htmlMessage,
bool $hasHtmlMessage,
array $attachments,
array $inlineAttachments,
bool $hasAttachments,
array $scheduling,
Horde_Imap_Client_DateTime $imapDate,
string $rawReferences,
string $dispositionNotificationTo,
string $rawInReplyTo,
bool $isEncrypted,
bool $isSigned,
bool $signatureIsValid,
Html $htmlService) {
$this->messageId = $uid;
$this->realMessageId = $messageId;
$this->flags = $flags;
$this->from = $from;
$this->to = $to;
$this->cc = $cc;
$this->bcc = $bcc;
$this->replyTo = $replyTo;
$this->subject = $subject;
$this->plainMessage = $plainMessage;
$this->htmlMessage = $htmlMessage;
$this->hasHtmlMessage = $hasHtmlMessage;
$this->attachments = $attachments;
$this->inlineAttachments = $inlineAttachments;
$this->hasAttachments = $hasAttachments;
$this->scheduling = $scheduling;
$this->imapDate = $imapDate;
$this->rawReferences = $rawReferences;
$this->dispositionNotificationTo = $dispositionNotificationTo;
$this->rawInReplyTo = $rawInReplyTo;
$this->isEncrypted = $isEncrypted;
$this->isSigned = $isSigned;
$this->signatureIsValid = $signatureIsValid;
$this->htmlService = $htmlService;
if (is_null($htmlService)) {
$urlGenerator = OC::$server->getURLGenerator();
$request = OC::$server->getRequest();
$this->htmlService = new Html($urlGenerator, $request);
}
if ($fetch === null) {
$this->loadMessageBodies();
} else {
$this->fetch = $fetch;
}
}
// output all the following:
public $header = null;
public $htmlMessage = '';
public $plainMessage = '';
public $attachments = [];
public $inlineAttachments = [];
public $scheduling = [];
private $loadHtmlMessage = false;
private $hasHtmlMessage = false;
/**
* @var Horde_Imap_Client_Socket
*/
private $conn;
/**
* @var Horde_Imap_Client_Mailbox
*/
private $mailBox;
private $messageId;
/**
* @var Horde_Imap_Client_Data_Fetch
*/
private $fetch;
public static function generateMessageId(): string {
return Horde_Mime_Headers_MessageId::create('nextcloud-mail-generated')->value;
}
@ -140,7 +139,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
* @return int
*/
public function getUid(): int {
return $this->fetch->getUid();
return $this->messageId;
}
/**
@ -148,17 +147,16 @@ class IMAPMessage implements IMessage, JsonSerializable {
* @return array
*/
public function getFlags(): array {
$flags = $this->fetch->getFlags();
return [
'seen' => in_array(Horde_Imap_Client::FLAG_SEEN, $flags),
'flagged' => in_array(Horde_Imap_Client::FLAG_FLAGGED, $flags),
'answered' => in_array(Horde_Imap_Client::FLAG_ANSWERED, $flags),
'deleted' => in_array(Horde_Imap_Client::FLAG_DELETED, $flags),
'draft' => in_array(Horde_Imap_Client::FLAG_DRAFT, $flags),
'forwarded' => in_array(Horde_Imap_Client::FLAG_FORWARDED, $flags),
'hasAttachments' => $this->hasAttachments($this->fetch->getStructure()),
'mdnsent' => in_array(Horde_Imap_Client::FLAG_MDNSENT, $flags, true),
'important' => in_array(Tag::LABEL_IMPORTANT, $flags, true)
'seen' => in_array(Horde_Imap_Client::FLAG_SEEN, $this->flags),
'flagged' => in_array(Horde_Imap_Client::FLAG_FLAGGED, $this->flags),
'answered' => in_array(Horde_Imap_Client::FLAG_ANSWERED, $this->flags),
'deleted' => in_array(Horde_Imap_Client::FLAG_DELETED, $this->flags),
'draft' => in_array(Horde_Imap_Client::FLAG_DRAFT, $this->flags),
'forwarded' => in_array(Horde_Imap_Client::FLAG_FORWARDED, $this->flags),
'hasAttachments' => $this->hasAttachments,
'mdnsent' => in_array(Horde_Imap_Client::FLAG_MDNSENT, $this->flags, true),
'important' => in_array(Tag::LABEL_IMPORTANT, $this->flags, true)
];
}
@ -175,46 +173,20 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('Not implemented');
}
/**
* @return Horde_Imap_Client_Data_Envelope
*/
public function getEnvelope() {
return $this->fetch->getEnvelope();
}
private function getRawReferences(): string {
/** @var resource $headersStream */
$headersStream = $this->fetch->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_STREAM);
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headersStream);
fclose($headersStream);
$references = $parsedHeaders->getHeader('references');
if ($references === null) {
return '';
}
return $references->value_single;
return $this->rawReferences;
}
private function getRawInReplyTo(): string {
return $this->fetch->getEnvelope()->in_reply_to;
return $this->rawInReplyTo;
}
public function getDispositionNotificationTo(): string {
/** @var resource $headersStream */
$headersStream = $this->fetch->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_STREAM);
$parsedHeaders = Horde_Mime_Headers::parseHeaders($headersStream);
fclose($headersStream);
$header = $parsedHeaders->getHeader('disposition-notification-to');
if ($header === null) {
return '';
}
return $header->value_single;
return $this->dispositionNotificationTo;
}
/**
* @return AddressList
*/
public function getFrom(): AddressList {
return AddressList::fromHorde($this->getEnvelope()->from);
return $this->from;
}
/**
@ -228,11 +200,8 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('IMAP message is immutable');
}
/**
* @return AddressList
*/
public function getTo(): AddressList {
return AddressList::fromHorde($this->getEnvelope()->to);
return $this->to;
}
/**
@ -246,11 +215,8 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('IMAP message is immutable');
}
/**
* @return AddressList
*/
public function getCC(): AddressList {
return AddressList::fromHorde($this->getEnvelope()->cc);
return $this->cc;
}
/**
@ -264,11 +230,8 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('IMAP message is immutable');
}
/**
* @return AddressList
*/
public function getBCC(): AddressList {
return AddressList::fromHorde($this->getEnvelope()->bcc);
return $this->bcc;
}
/**
@ -282,27 +245,12 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('IMAP message is immutable');
}
/**
* Get the ID if available
*
* @return string
*/
public function getMessageId(): string {
return $this->getEnvelope()->message_id;
return $this->realMessageId;
}
/**
* @return string
*/
public function getSubject(): string {
// Try a soft conversion first (some installations, eg: Alpine linux,
// have issues with the '//IGNORE' option)
$subject = $this->getEnvelope()->subject;
$utf8 = iconv('UTF-8', 'UTF-8', $subject);
if ($utf8 !== false) {
return $utf8;
}
return iconv("UTF-8", "UTF-8//IGNORE", $subject);
return $this->subject;
}
/**
@ -316,186 +264,8 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('IMAP message is immutable');
}
/**
* @return Horde_Imap_Client_DateTime
*/
public function getSentDate(): Horde_Imap_Client_DateTime {
return $this->fetch->getImapDate();
}
/**
* @return int
*/
public function getSize(): int {
return $this->fetch->getSize();
}
/**
* @param Horde_Mime_Part $part
*
* @return bool
*/
private function hasAttachments($part) {
foreach ($part->getParts() as $p) {
if ($p->isAttachment() || $p->getType() === 'message/rfc822') {
return true;
}
if ($this->hasAttachments($p)) {
return true;
}
}
return false;
}
private function loadMessageBodies(): void {
$fetch_query = new Horde_Imap_Client_Fetch_Query();
$fetch_query->envelope();
$fetch_query->structure();
$fetch_query->flags();
$fetch_query->size();
$fetch_query->imapDate();
$fetch_query->headerText([
'cache' => true,
'peek' => true,
]);
// $list is an array of Horde_Imap_Client_Data_Fetch objects.
$ids = new Horde_Imap_Client_Ids($this->messageId);
$headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]);
/** @var Horde_Imap_Client_Data_Fetch $fetch */
$fetch = $headers[$this->messageId];
if (is_null($fetch)) {
throw new DoesNotExistException("This email ($this->messageId) can't be found. Probably it was deleted from the server recently. Please reload.");
}
// set $this->fetch to get to, from ...
$this->fetch = $fetch;
// analyse the body part
$structure = $fetch->getStructure();
// debugging below
$structure_type = $structure->getPrimaryType();
if ($structure_type === 'multipart') {
$i = 1;
foreach ($structure->getParts() as $p) {
$this->getPart($p, $i++);
}
} else {
if (!is_null($structure->findBody())) {
// get the body from the server
$partId = (int)$structure->findBody();
$this->getPart($structure->getPart($partId), $partId);
}
}
}
/**
* @param Horde_Mime_Part $p
* @param mixed $partNo
*
* @throws DoesNotExistException
*
* @return void
*/
private function getPart(Horde_Mime_Part $p, $partNo): void {
// iMIP messages
// Handle text/calendar parts first because they might be attachments at the same time.
// Otherwise, some of the following if-conditions might break the handling and treat iMIP
// data like regular attachments.
$allContentTypeParameters = $p->getAllContentTypeParameters();
if ($p->getType() === 'text/calendar') {
// Handle event data like a regular attachment
// Outlook doesn't set a content disposition
// We work around this by checking for the name only
if ($p->getName() !== null) {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'fileName' => $p->getName(),
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
}
// return if this is an event attachment only
// the method parameter determines if this is a iMIP message
if (!isset($allContentTypeParameters['method'])) {
return;
}
if (in_array(strtoupper($allContentTypeParameters['method']), ['REQUEST', 'REPLY', 'CANCEL'])) {
$this->scheduling[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'method' => strtoupper($allContentTypeParameters['method']),
'contents' => $this->loadBodyData($p, $partNo),
];
return;
}
}
// Regular attachments
if ($p->isAttachment() || $p->getType() === 'message/rfc822') {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'fileName' => $p->getName(),
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
return;
}
// Inline attachments
// Horde doesn't consider parts with content-disposition set to inline as
// attachment so we need to use another way to get them.
// We use these inline attachments to render a message's html body in $this->getHtmlBody()
$filename = $p->getName();
if ($p->getType() === 'message/rfc822' || isset($filename)) {
if (in_array($filename, $this->attachmentsToIgnore)) {
return;
}
$this->inlineAttachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'fileName' => $filename,
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId()
];
return;
}
if ($p->getPrimaryType() === 'multipart') {
$this->handleMultiPartMessage($p, $partNo);
return;
}
if ($p->getType() === 'text/plain') {
$this->handleTextMessage($p, $partNo);
return;
}
if ($p->getType() === 'text/html') {
$this->handleHtmlMessage($p, $partNo);
return;
}
// EMBEDDED MESSAGE
// Many bounce notifications embed the original message as type 2,
// but AOL uses type 1 (multipart), which is not handled here.
// There are no PHP functions to parse embedded messages,
// so this just appends the raw source to the main message.
if ($p[0] === 'message') {
$data = $this->loadBodyData($p, $partNo);
$this->plainMessage .= trim($data) . "\n\n";
}
return $this->imapDate;
}
/**
@ -567,102 +337,6 @@ class IMAPMessage implements IMessage, JsonSerializable {
return $this->plainMessage;
}
/**
* @param Horde_Mime_Part $part
* @param mixed $partNo
*
* @throws DoesNotExistException
*
* @return void
*/
private function handleMultiPartMessage(Horde_Mime_Part $part, $partNo): void {
$i = 1;
foreach ($part->getParts() as $p) {
$this->getPart($p, "$partNo.$i");
$i++;
}
}
/**
* @param Horde_Mime_Part $p
* @param mixed $partNo
*
* @throws DoesNotExistException
*
* @return void
*/
private function handleTextMessage(Horde_Mime_Part $p, $partNo): void {
$data = $this->loadBodyData($p, $partNo);
$this->plainMessage .= trim($data) . "\n\n";
}
/**
* @param Horde_Mime_Part $p
* @param mixed $partNo
*
* @throws DoesNotExistException
*
* @return void
*/
private function handleHtmlMessage(Horde_Mime_Part $p, $partNo): void {
$this->hasHtmlMessage = true;
if ($this->loadHtmlMessage) {
$data = $this->loadBodyData($p, $partNo);
$this->htmlMessage .= $data . "<br><br>";
}
}
/**
* @param Horde_Mime_Part $p
* @param mixed $partNo
*
* @return string
* @throws DoesNotExistException
* @throws Exception
*/
private function loadBodyData(Horde_Mime_Part $p, $partNo): string {
// DECODE DATA
$fetch_query = new Horde_Imap_Client_Fetch_Query();
$ids = new Horde_Imap_Client_Ids($this->messageId);
$fetch_query->bodyPart($partNo, [
'peek' => true
]);
$fetch_query->bodyPartSize($partNo);
$fetch_query->mimeHeader($partNo, [
'peek' => true
]);
$headers = $this->conn->fetch($this->mailBox, $fetch_query, ['ids' => $ids]);
/* @var $fetch Horde_Imap_Client_Data_Fetch */
$fetch = $headers[$this->messageId];
if (is_null($fetch)) {
throw new DoesNotExistException("Mail body for this mail($this->messageId) could not be loaded");
}
$mimeHeaders = $fetch->getMimeHeader($partNo, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if ($enc = $mimeHeaders->getValue('content-transfer-encoding')) {
$p->setTransferEncoding($enc);
}
$data = $fetch->getBodyPart($partNo);
$p->setContents($data);
$data = $p->getContents();
if ($data === null) {
return '';
}
// Only convert encoding if it is explicitly specified in the header because text/calendar
// data is utf-8 by default.
$charset = $p->getContentTypeParameter('charset');
if ($charset !== null && strtoupper($charset) !== 'UTF-8') {
$data = mb_convert_encoding($data, 'UTF-8', $charset);
}
return (string)$data;
}
public function getContent(): string {
return $this->getPlainBody();
}
@ -736,11 +410,8 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('not implemented');
}
/**
* @return AddressList
*/
public function getReplyTo() {
return AddressList::fromHorde($this->getEnvelope()->reply_to);
public function getReplyTo(): AddressList {
return $this->replyTo;
}
/**
@ -752,6 +423,18 @@ class IMAPMessage implements IMessage, JsonSerializable {
throw new Exception('not implemented');
}
public function isEncrypted(): bool {
return $this->isEncrypted;
}
public function isSigned(): bool {
return $this->isSigned;
}
public function isSignatureValid(): bool {
return $this->signatureIsValid;
}
/**
* Cast all values from an IMAP message into the correct DB format
*
@ -783,7 +466,7 @@ class IMAPMessage implements IMessage, JsonSerializable {
$msg->setSubject(mb_strcut($this->getSubject(), 0, 255));
$msg->setSentAt($this->getSentDate()->getTimestamp());
$flags = $this->fetch->getFlags();
$flags = $this->flags;
$msg->setFlagAnswered(in_array(Horde_Imap_Client::FLAG_ANSWERED, $flags, true));
$msg->setFlagDeleted(in_array(Horde_Imap_Client::FLAG_DELETED, $flags, true));
$msg->setFlagDraft(in_array(Horde_Imap_Client::FLAG_DRAFT, $flags, true));

Просмотреть файл

@ -30,10 +30,12 @@ use JsonSerializable;
class SmimeData implements JsonSerializable {
private bool $isSigned;
private ?bool $signatureIsValid;
private bool $isEncrypted;
public function __construct() {
$this->isSigned = false;
$this->signatureIsValid = null;
$this->isEncrypted = false;
}
/**
@ -64,11 +66,27 @@ class SmimeData implements JsonSerializable {
$this->signatureIsValid = $signatureIsValid;
}
/**
* @return bool
*/
public function isEncrypted(): bool {
return $this->isEncrypted;
}
/**
* @param bool $isEncrypted
* @return void
*/
public function setIsEncrypted(bool $isEncrypted): void {
$this->isEncrypted = $isEncrypted;
}
#[\ReturnTypeWillChange]
public function jsonSerialize() {
return [
'isSigned' => $this->isSigned,
'signatureIsValid' => $this->signatureIsValid,
'isEncrypted' => $this->isEncrypted,
];
}
}

Просмотреть файл

@ -5,6 +5,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Luc Calaresu <dev@calaresu.com>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -279,7 +280,8 @@ class AttachmentService implements IAttachmentService {
$fullText = $this->messageMapper->getFullText(
$client,
$mailbox->getName(),
$attachmentMessage->getUid()
$attachmentMessage->getUid(),
$account->getUserId()
);
// detect mime type
@ -317,6 +319,7 @@ class AttachmentService implements IAttachmentService {
$client,
$mailbox->getName(),
(int)$attachment['uid'],
$account->getUserId(),
[
$attachment['id'] ?? []
]

Просмотреть файл

@ -87,7 +87,7 @@ class ItineraryService {
$client = $this->clientFactory->getClient($account);
try {
$itinerary = new Itinerary();
$htmlBody = $this->messageMapper->getHtmlBody($client, $mailbox->getName(), $id);
$htmlBody = $this->messageMapper->getHtmlBody($client, $mailbox->getName(), $id, $account->getUserId());
if ($htmlBody !== null) {
$itinerary = $itinerary->merge(
$this->extractor->extract($htmlBody)
@ -96,7 +96,7 @@ class ItineraryService {
} else {
$this->logger->debug('Message does not have an HTML body, can\'t extract itinerary info');
}
$attachments = $this->messageMapper->getRawAttachments($client, $mailbox->getName(), $id);
$attachments = $this->messageMapper->getRawAttachments($client, $mailbox->getName(), $id, $account->getUserId());
} finally {
$client->logout();
}

Просмотреть файл

@ -29,7 +29,9 @@ use Horde_Imap_Client_Exception;
use Horde_Imap_Client_Exception_NoSupportExtension;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Socket;
use Horde_Mime_Exception;
use OCA\Mail\Account;
use OCA\Mail\Attachment;
use OCA\Mail\Contracts\IMailManager;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
@ -192,6 +194,7 @@ class MailManager implements IMailManager {
$client,
$mailbox->getName(),
$uid,
$account->getUserId(),
$loadBody
);
} catch (Horde_Imap_Client_Exception | DoesNotExistException $e) {
@ -219,6 +222,7 @@ class MailManager implements IMailManager {
$client,
$mailbox->getName(),
new Horde_Imap_Client_Ids($uids),
$account->getUserId(),
true
);
} catch (Horde_Imap_Client_Exception $e) {
@ -262,7 +266,9 @@ class MailManager implements IMailManager {
return $this->imapMessageMapper->getFullText(
$client,
$mailbox,
$uid
$uid,
$account->getUserId(),
false,
);
} catch (Horde_Imap_Client_Exception | DoesNotExistException $e) {
throw new ServiceException("Could not load message", 0, $e);
@ -664,7 +670,43 @@ class MailManager implements IMailManager {
public function getMailAttachments(Account $account, Mailbox $mailbox, Message $message): array {
$client = $this->imapClientFactory->getClient($account);
try {
return $this->imapMessageMapper->getAttachments($client, $mailbox->getName(), $message->getUid());
return $this->imapMessageMapper->getAttachments(
$client,
$mailbox->getName(),
$message->getUid(),
$account->getUserId(),
);
} finally {
$client->logout();
}
}
/**
* @param Account $account
* @param Mailbox $mailbox
* @param Message $message
* @param string $attachmentId
* @return Attachment
*
* @throws DoesNotExistException
* @throws Horde_Imap_Client_Exception
* @throws Horde_Imap_Client_Exception_NoSupportExtension
* @throws ServiceException
* @throws Horde_Mime_Exception
*/
public function getMailAttachment(Account $account,
Mailbox $mailbox,
Message $message,
string $attachmentId): Attachment {
$client = $this->imapClientFactory->getClient($account);
try {
return $this->imapMessageMapper->getAttachment(
$client,
$mailbox->getName(),
$message->getUid(),
$attachmentId,
$account->getUserId(),
);
} finally {
$client->logout();
}

Просмотреть файл

@ -497,7 +497,8 @@ class MailTransmission implements IMailTransmission {
$fullText = $this->messageMapper->getFullText(
$client,
$mailbox->getName(),
$attachmentMessage->getUid()
$attachmentMessage->getUid(),
$userId
);
} finally {
$client->logout();
@ -527,7 +528,8 @@ class MailTransmission implements IMailTransmission {
$fullText = $this->messageMapper->getFullText(
$client,
$mailbox->getName(),
$attachmentMessage->getUid()
$attachmentMessage->getUid(),
$userId
);
} finally {
$client->logout();
@ -558,6 +560,7 @@ class MailTransmission implements IMailTransmission {
$client,
$mailbox->getName(),
$attachmentMessage->getUid(),
$userId,
[
$attachment['id']
]

Просмотреть файл

@ -26,12 +26,19 @@ declare(strict_types=1);
namespace OCA\Mail\Service;
use Exception;
use Horde_Imap_Client;
use Horde_Imap_Client_Data_Fetch;
use Horde_Imap_Client_Fetch_Query;
use Horde_Mail_Rfc822_Address;
use Horde_Mime_Exception;
use Horde_Mime_Headers;
use Horde_Mime_Headers_ContentParam_ContentType;
use Horde_Mime_Part;
use OCA\Mail\Db\SmimeCertificate;
use OCA\Mail\Db\SmimeCertificateMapper;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Exception\SmimeCertificateParserException;
use OCA\Mail\Exception\SmimeDecryptException;
use OCA\Mail\Exception\SmimeSignException;
use OCA\Mail\Model\EnrichedSmimeCertificate;
use OCA\Mail\Model\SmimeCertificateInfo;
@ -63,6 +70,7 @@ class SmimeService {
/**
* Attempt to verify a message signed with S/MIME.
*
* Requires the openssl extension.
*
* @param string $message Whole message including all headers and parts as stored on IMAP
@ -78,9 +86,13 @@ class SmimeService {
fwrite($messageTempHandle, $message);
fclose($messageTempHandle);
/** @psalm-suppress NullArgument */
$valid = openssl_pkcs7_verify($messageTemp, 0, null, [
$this->certificateManager->getAbsoluteBundlePath(),
]);
$valid = openssl_pkcs7_verify(
$messageTemp,
0,
null,
[$this->certificateManager->getAbsoluteBundlePath()],
);
if (is_int($valid)) {
// OpenSSL error
return false;
@ -89,6 +101,52 @@ class SmimeService {
return $valid;
}
/**
* Attempt to extract the signed content from a signed S/MIME message.
* Can be used to extract opaque signed content even if the signature itself can't be verified.
*
* Warning: This method does not attempt to verify the signature.
*
* Requires the openssl extension.
*
* @param string $message Whole message including all headers and parts as stored on IMAP
* @return string Signed content
*
* @throws ServiceException If no signed content can be extracted
*/
public function extractSignedContent(string $message): string {
// Ideally, we should use the more modern openssl cms module as it is a superset of the
// smime/pkcs7 module. Unfortunately, it is only supported since php 8.
// Ref https://www.php.net/manual/en/function.openssl-cms-verify.php
$verifiedContentTemp = $this->tempManager->getTemporaryFile();
$messageTemp = $this->tempManager->getTemporaryFile();
$messageTempHandle = fopen($messageTemp, 'wb');
fwrite($messageTempHandle, $message);
fclose($messageTempHandle);
/** @psalm-suppress NullArgument */
$valid = openssl_pkcs7_verify(
$messageTemp,
PKCS7_NOSIGS | PKCS7_NOVERIFY,
null,
[$this->certificateManager->getAbsoluteBundlePath()],
null,
$verifiedContentTemp,
);
if (is_int($valid)) {
// OpenSSL error
throw new ServiceException('Failed to extract signed content');
}
$verifiedContent = file_get_contents($verifiedContentTemp);
if ($verifiedContent === false) {
throw new ServiceException('Could not read back verified content');
}
return $verifiedContent;
}
/**
* Parse a X509 certificate.
*
@ -182,6 +240,28 @@ class SmimeService {
return $this->certificateMapper->find($certificateId, $userId);
}
/**
* Get all S/MIME certificates belonging to an email address.
*
* @param string $emailAddress
* @param string $userId
* @return SmimeCertificate[]
*
* @throws ServiceException If the database query fails
*/
public function findCertificatesByEmailAddress(string $emailAddress,
string $userId): array {
try {
return $this->certificateMapper->findAllByEmailAddress($userId, $emailAddress);
} catch (\OCP\DB\Exception $e) {
throw new ServiceException(
'Failed to fetch certificates by email address: ' . $e->getMessage(),
0,
$e,
);
}
}
/**
* Find all S/MIME certificates of the given user.
*
@ -293,4 +373,152 @@ class SmimeService {
return $parsedPart;
}
/**
* Decrypt full text of a MIME message.
* This method assumes the given mime part text to be encrypted without checking.
*
* @param string $mimePartText
* @param SmimeCertificate $certificate The certificate needs to contain a private key.
* @return string Full text of decrypted MIME message. It will probably contain multiple parts.
*
* @throws ServiceException If the given certificate does not have a private key or can't be decrypted
* @throws SmimeDecryptException If openssl reports an error during decryption
*/
public function decryptMimePartText(string $mimePartText,
SmimeCertificate $certificate): string {
if ($certificate->getPrivateKey() === null) {
throw new ServiceException('Certificate does not have a private key');
}
try {
$decryptedCertificate = $this->crypto->decrypt($certificate->getCertificate());
$decryptedKey = $this->crypto->decrypt($certificate->getPrivateKey());
} catch (Exception $e) {
throw new ServiceException(
'Failed to decrypt certificate or private key: ' . $e->getMessage(),
0,
$e,
);
}
$inPath = $this->tempManager->getTemporaryFile();
$outPath = $this->tempManager->getTemporaryFile();
file_put_contents($inPath, $mimePartText);
if (!openssl_pkcs7_decrypt($inPath, $outPath, $decryptedCertificate, $decryptedKey)) {
throw new SmimeDecryptException('Failed to decrypt MIME part text');
}
$decryptedMessage = file_get_contents($outPath);
// Handle smime-type="signed-data" as the content is opaque until verified
$headers = Horde_Mime_Headers::parseHeaders($decryptedMessage);
if (!isset($headers['content-type'])) {
return $decryptedMessage;
}
/** @var Horde_Mime_Headers_ContentParam_ContentType $contentType */
$contentType = $headers['content-type'];
if ($contentType->ptype !== 'application'
|| $contentType->stype !== 'pkcs7-mime'
|| !isset($contentType['smime-type'])
|| $contentType['smime-type'] !== 'signed-data') {
return $decryptedMessage;
}
try {
// TODO: propagate signature verification status
$decryptedMessage = $this->extractSignedContent($decryptedMessage);
} catch (ServiceException $e) {
throw new ServiceException(
'Failed to extract nested signed data: ' . $e->getMessage(),
0,
$e,
);
}
return $decryptedMessage;
}
/**
* Try to decrypt a raw data fetch from horde.
* The fetch needs to contain at least envelope, headerText and fullText.
* See the addDecryptQueries() method.
*
* This method will do nothing to the full text if the message is not encrypted.
*
* @param Horde_Imap_Client_Data_Fetch $message
* @param string $userId
* @return string
*
* @throws ServiceException
*/
public function decryptDataFetch(Horde_Imap_Client_Data_Fetch $message, string $userId): string {
$encryptedText = $message->getFullMsg();
if (!$this->isEncrypted($message)) {
return $encryptedText;
}
$decryptedText = null;
$envelope = $message->getEnvelope();
foreach ($envelope->to as $recipient) {
/** @var Horde_Mail_Rfc822_Address $recipient */
$recipientAddress = $recipient->bare_address;
$certs = $this->findCertificatesByEmailAddress(
$recipientAddress,
$userId,
);
foreach ($certs as $cert) {
try {
$decryptedText = $this->decryptMimePartText($encryptedText, $cert);
} catch (ServiceException | SmimeDecryptException $e) {
// Certificate probably didn't match -> continue
// TODO: filter a real decryption error
// (is hard because openssl doesn't return a proper error code)
continue;
}
}
}
if ($decryptedText === null) {
throw new ServiceException('Failed to find a suitable S/MIME certificate for decryption');
}
return $decryptedText;
}
public function addEncryptionCheckQueries(Horde_Imap_Client_Fetch_Query $query,
bool $peek = true): void {
if (!$query->contains(Horde_Imap_Client::FETCH_HEADERTEXT)) {
$query->headerText([
'peek' => $peek,
]);
}
}
public function addDecryptQueries(Horde_Imap_Client_Fetch_Query $query,
bool $peek = true): void {
$this->addEncryptionCheckQueries($query, $peek);
$query->envelope();
if (!$query->contains(Horde_Imap_Client::FETCH_FULLMSG)) {
$query->fullText([
'peek' => $peek,
]);
}
}
public function isEncrypted(Horde_Imap_Client_Data_Fetch $message): bool {
$headers = $message->getHeaderText('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
if (!isset($headers['content-type'])) {
return false;
}
/** @var Horde_Mime_Headers_ContentParam_ContentType $contentType */
$contentType = $headers['content-type'];
return $contentType->ptype === 'application'
&& $contentType->stype === 'pkcs7-mime'
&& isset($contentType['smime-type'])
&& $contentType['smime-type'] === 'enveloped-data';
}
}

Просмотреть файл

@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
* @author 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @license GNU AGPL version 3 or any later version
*
@ -302,7 +303,8 @@ class ImapToDbSynchronizer {
self::MAX_NEW_MESSAGES,
$highestKnownUid ?? 0,
$logger,
$perf
$perf,
$account->getUserId(),
);
$perf->step(sprintf('fetch %d messages from IMAP', count($imapMessages)));
} catch (Horde_Imap_Client_Exception $e) {
@ -372,6 +374,7 @@ class ImapToDbSynchronizer {
$mailbox->getSyncNewToken(),
$uids
),
$account->getUserId(),
Horde_Imap_Client::SYNC_NEWMSGSUIDS
);
$perf->step('get new messages via Horde');
@ -415,6 +418,7 @@ class ImapToDbSynchronizer {
$mailbox->getSyncChangedToken(),
$uids
),
$account->getUserId(),
Horde_Imap_Client::SYNC_FLAGSUIDS
);
$perf->step('get changed messages via Horde');
@ -444,6 +448,7 @@ class ImapToDbSynchronizer {
$mailbox->getSyncVanishedToken(),
$uids
),
$account->getUserId(),
Horde_Imap_Client::SYNC_VANISHEDUIDS
);
$perf->step('get vanished messages via Horde');

Просмотреть файл

@ -63,7 +63,7 @@
{{ subjectForSubtitle }}
</span>
</div>
<div v-if="data.previewText"
<div v-if="data.encrypted || data.previewText"
class="envelope__preview-text">
{{ isEncrypted ? t('mail', 'Encrypted message') : data.previewText }}
</div>
@ -435,8 +435,8 @@ export default {
return this.data.flags.seen
},
isEncrypted() {
return this.data.previewText
&& isPgpText(this.data.previewText)
return this.data.encrypted // S/MIME
|| (this.data.previewText && isPgpText(this.data.previewText)) // PGP/Mailvelope
},
isImportant() {
return this.$store.getters

Просмотреть файл

@ -77,9 +77,12 @@
<div class="right">
<Moment class="timestamp" :timestamp="envelope.dateInt" />
<template v-if="expanded">
<NcActions v-if="smimeData.isSigned">
<NcActions v-if="smimeData.isSigned || smimeData.isEncrypted">
<template #icon>
<LockIcon v-if="smimeData.signatureIsValid"
<LockIcon v-if="smimeData.isEncrypted"
:size="20"
fill-color="#008000" />
<LockIcon v-else-if="smimeData.signatureIsValid"
:size="20"
fill-color="#ffcc00" />
<LockOffIcon v-else
@ -87,7 +90,7 @@
fill-color="red" />
</template>
<NcActionText>
{{ smimeSignMessage }}
{{ smimeMessage }}
</NcActionText>
<!-- TODO: display information about signer and/or CA certificate -->
</NcActions>
@ -380,12 +383,16 @@ export default {
smimeData() {
return this.message?.smime ?? {}
},
smimeSignMessage() {
smimeMessage() {
if (this.smimeData.isEncrypted) {
return t('mail', 'This message was encrypted by the sender before it was sent.')
}
if (this.smimeData.signatureIsValid) {
return t('mail', 'This message contains a verified digital S/MIME signature. The message wasn\'t changed since it was sent.')
} else {
return t('mail', 'This message contains an unverified digital S/MIME signature. The message might have been changed since it was sent or the certificate of the signer is untrusted.')
}
return t('mail', 'This message contains an unverified digital S/MIME signature. The message might have been changed since it was sent or the certificate of the signer is untrusted.')
},
},
watch: {

Просмотреть файл

@ -27,6 +27,7 @@ use Horde_Imap_Client_Fetch_Query;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Socket;
use Horde_Mail_Rfc822_Address;
use Horde_Mime_Headers;
use Horde_Mime_Headers_MessageId;
use Horde_Mime_Mail;
use Horde_Mime_Part;
@ -170,6 +171,33 @@ trait ImapTest {
}
}
/**
* @param string $mailbox
* @param string $mimeText
* @param MailAccount|null $account
* @return int Uid of the new message
*/
public function saveMimeMessage(string $mailbox, string $mimeText, ?MailAccount $account = null): int {
$headers = Horde_Mime_Headers::parseHeaders($mimeText);
$mimePart = Horde_Mime_Part::parseMessage($mimeText);
$mail = new Horde_Mime_Mail();
$mail->addHeaders($headers);
$mail->setBasePart($mimePart);
$data = $mail->getRaw(false);
$client = $this->getClient($account);
try {
return $client->append($mailbox, [
[
'data' => $mimeText,
]
])->ids[0];
} finally {
$client->logout();
}
}
public function flagMessage($mailbox, $id, MailAccount $account = null) {
$client = $this->getClient($account);
try {

Просмотреть файл

@ -4,6 +4,7 @@
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Christoph Wurst <wurst.christoph@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -29,7 +30,6 @@ use OC;
use OCA\Mail\Account;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Exception\ServiceException;
use OCA\Mail\Mailbox;
/**
* @group IMAP
@ -108,23 +108,4 @@ abstract class AbstractTest extends TestCase {
protected function assertMailBoxNotExists($name) {
$this->assertFalse($this->existsMailBox($name));
}
protected function createTestMessage(
Mailbox $mailbox, $subject = 'Don\'t panic!',
$contents = 'Don\'t forget your towel', $from = 'someone@there.com',
$to = 'me@here.com'
) {
$message = "From: $from
Subject: $subject
To: $to
Message-ID: <20150415133206.Horde.M8uzSs0lxFX6uUE2sc6_rw5@localhost>
User-Agent: Horde Application Framework 5
Date: Wed, 15 Apr 2015 13:32:06 +0000
Content-Type: text/plain; charset=UTF-8; format=flowed; DelSp=Yes
MIME-Version: 1.0
$contents";
$mailbox->saveMessage($message);
}
}

Просмотреть файл

@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* 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, or
* (at your option) any later version.
*
* 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 OCA\Mail\Tests\Integration\IMAP;
use OC;
use OCA\Mail\Db\MailAccount;
use OCA\Mail\Db\SmimeCertificate;
use OCA\Mail\Db\SmimeCertificateMapper;
use OCA\Mail\IMAP\ImapMessageFetcherFactory;
use OCA\Mail\Tests\Integration\Framework\ImapTest;
use OCA\Mail\Tests\Integration\Framework\ImapTestAccount;
use OCA\Mail\Tests\Integration\TestCase;
use OCP\ICertificateManager;
use OCP\Security\ICrypto;
class ImapMessageFetcherIntegrationTest extends TestCase {
use ImapTest,
ImapTestAccount;
private MailAccount $account;
private ImapMessageFetcherFactory $fetcherFactory;
private SmimeCertificateMapper $certificateMapper;
private ICrypto $crypto;
private ICertificateManager $certificateManager;
protected function setUp(): void {
parent::setUp();
$this->account = $this->createTestAccount();
$this->fetcherFactory = OC::$server->get(ImapMessageFetcherFactory::class);
$this->certificateMapper = OC::$server->get(SmimeCertificateMapper::class);
$this->crypto = OC::$server->get(ICrypto::class);
$this->certificateManager = OC::$server->get(ICertificateManager::class);
$this->certificateManager->addCertificate(
file_get_contents(__DIR__ . '/../../data/smime-certs/domain.tld.ca.crt'),
'domain.tld.ca.crt'
);
$this->importCertificate('user@imap.localhost');
$this->importCertificate('user@domain.tld');
}
protected function tearDown(): void {
parent::tearDown();
$this->certificateManager->removeCertificate('domain.tld.ca.crt');
$this->clearCertificates();
}
private function importCertificate(string $emailAddress): SmimeCertificate {
// TODO: convert to a trait?!
$certificateData = file_get_contents(__DIR__ . "/../../data/smime-certs/$emailAddress.crt");
$privateKeyData = file_get_contents(__DIR__ . "/../../data/smime-certs/$emailAddress.key");
$certificate = new SmimeCertificate();
$certificate->setUserId($this->account->getUserId());
$certificate->setEmailAddress($emailAddress);
$certificate->setCertificate($this->crypto->encrypt($certificateData));
$certificate->setPrivateKey($this->crypto->encrypt($privateKeyData));
$this->certificateMapper->insert($certificate);
return $certificate;
}
private function clearCertificates(): void {
// TODO: convert to a trait?!
$certificates = $this->certificateMapper->findAll($this->account->getUserId());
foreach ($certificates as $certificate) {
$this->certificateMapper->delete($certificate);
}
}
public function testFetchMessageWithEncryptedMessage(): void {
$encryptedMessage = file_get_contents(__DIR__ . '/../../data/encrypted-message.txt');
$uid = $this->saveMimeMessage('INBOX', $encryptedMessage);
$fetcher = $this->fetcherFactory
->build(
$uid,
'INBOX',
$this->getTestClient(),
$this->account->getUserId()
)
->withBody(true);
$message = $fetcher->fetchMessage();
$this->assertEquals("Just some encrypted test images.\n\n", $message->getPlainBody());
$this->assertCount(3, $message->attachments);
}
public function testFetchMessageWithEncryptedSignedOpaqueMessage(): void {
$encryptedMessage = file_get_contents(__DIR__ . '/../../data/encrypted-signed-opaque-message.txt');
$uid = $this->saveMimeMessage('INBOX', $encryptedMessage);
$fetcher = $this->fetcherFactory
->build(
$uid,
'INBOX',
$this->getTestClient(),
$this->account->getUserId()
)
->withBody(true);
$message = $fetcher->fetchMessage();
$this->assertEquals("hoi\n\n", $message->getPlainBody());
}
}

Просмотреть файл

@ -32,7 +32,6 @@ use OCP\IL10N;
use OCP\IRequest;
use OCA\Mail\Db\Tag;
use OCA\Mail\Account;
use OCA\Mail\Mailbox;
use OCP\Files\Folder;
use ReflectionObject;
use OCP\IURLGenerator;
@ -101,9 +100,6 @@ class MessagesControllerTest extends TestCase {
/** @var MockObject|Account */
private $account;
/** @var MockObject|Mailbox */
private $mailbox;
/** @var MockObject|Message */
private $message;
@ -186,7 +182,6 @@ class MessagesControllerTest extends TestCase {
);
$this->account = $this->createMock(Account::class);
$this->mailbox = $this->createMock(Mailbox::class);
$this->message = $this->createMock(IMAPMessage::class);
$this->attachment = $this->createMock(Attachment::class);
}
@ -298,17 +293,14 @@ class MessagesControllerTest extends TestCase {
->method('getMailbox')
->with($this->userId, $mailboxId)
->willReturn($mailbox);
$this->mailManager->expects($this->once())
->method('getMailAttachment')
->with($this->account, $mailbox, $message, $attachmentId)
->will($this->returnValue($this->attachment));
$this->accountService->expects($this->once())
->method('find')
->with($this->equalTo($this->userId), $this->equalTo($accountId))
->will($this->returnValue($this->account));
$this->account->expects($this->once())
->method('getMailbox')
->willReturn($this->mailbox);
$this->mailbox->expects($this->once())
->method('getAttachment')
->with($uid, $attachmentId)
->will($this->returnValue($this->attachment));
$this->attachment->expects($this->once())
->method('getContents')
->will($this->returnValue($contents));
@ -353,13 +345,9 @@ class MessagesControllerTest extends TestCase {
->method('find')
->with($this->equalTo($this->userId), $this->equalTo($accountId))
->will($this->returnValue($this->account));
$this->account->expects($this->once())
->method('getMailbox')
->with('INBOX')
->will($this->returnValue($this->mailbox));
$this->mailbox->expects($this->once())
->method('getAttachment')
->with($uid, $attachmentId)
$this->mailManager->expects($this->once())
->method('getMailAttachment')
->with($this->account, $mailbox, $message, $attachmentId)
->will($this->returnValue($this->attachment));
$this->attachment->expects($this->once())
->method('getName')
@ -406,6 +394,7 @@ class MessagesControllerTest extends TestCase {
$mailbox = new \OCA\Mail\Db\Mailbox();
$mailbox->setName('INBOX');
$mailbox->setAccountId($accountId);
$client = $this->createMock(Horde_Imap_Client_Socket::class);
$this->mailManager->expects($this->once())
->method('getMessage')
->with($this->userId, $id)
@ -418,23 +407,23 @@ class MessagesControllerTest extends TestCase {
->method('find')
->with($this->equalTo($this->userId), $this->equalTo($accountId))
->will($this->returnValue($this->account));
$this->account->expects($this->once())
->method('getMailbox')
->with('INBOX')
->will($this->returnValue($this->mailbox));
$this->mailbox->expects($this->once())
->method('getMessage')
->with($uid)
->will($this->returnValue($this->message));
$this->clientFactory->expects($this->once())
->method('getClient')
->with($this->account)
->willReturn($client);
$this->mailManager->expects($this->once())
->method('getImapMessage')
->with($client, $this->account, $mailbox, $message->getUid(), true)
->willReturn($this->message);
$this->message->attachments = [
[
'id' => $attachmentId
]
];
$this->mailbox->expects($this->once())
->method('getAttachment')
->with($uid, $attachmentId)
$this->mailManager->expects($this->once())
->method('getMailAttachment')
->with($this->account, $mailbox, $message, $attachmentId)
->will($this->returnValue($this->attachment));
$this->attachment->expects($this->once())
->method('getName')

Просмотреть файл

@ -4,6 +4,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -31,8 +32,11 @@ use Horde_Imap_Client_Fetch_Results;
use Horde_Imap_Client_Ids;
use Horde_Imap_Client_Socket;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\IMAP\ImapMessageFetcher;
use OCA\Mail\IMAP\ImapMessageFetcherFactory;
use OCA\Mail\IMAP\MessageMapper;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\SmimeService;
use OCA\Mail\Support\PerformanceLoggerTask;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@ -45,13 +49,23 @@ class MessageMapperTest extends TestCase {
/** @var MessageMapper */
private $mapper;
/** @var SmimeService|MockObject */
private $sMimeService;
/** @var ImapMessageFetcherFactory|MockObject */
private $imapMessageFactory;
protected function setUp(): void {
parent::setUp();
$this->logger = $this->createMock(LoggerInterface::class);
$this->sMimeService = $this->createMock(SmimeService::class);
$this->imapMessageFactory = $this->createMock(ImapMessageFetcherFactory::class);
$this->mapper = new MessageMapper(
$this->logger
$this->logger,
$this->sMimeService,
$this->imapMessageFactory,
);
}
@ -60,6 +74,13 @@ class MessageMapperTest extends TestCase {
$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
$mailbox = 'inbox';
$ids = [1, 3];
$userId = 'user';
$loadBody = false;
$imapMessageFetcher1 = $this->createMock(ImapMessageFetcher::class);
$imapMessageFetcher2 = $this->createMock(ImapMessageFetcher::class);
$message1 = $this->createMock(IMAPMessage::class);
$message2 = $this->createMock(IMAPMessage::class);
$fetchResults = new Horde_Imap_Client_Fetch_Results();
$fetchResult1 = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
@ -81,15 +102,42 @@ class MessageMapperTest extends TestCase {
->willReturn(1);
$fetchResult2->method('getUid')
->willReturn(3);
$this->imapMessageFactory->expects(self::exactly(2))
->method('build')
->willReturnMap([
[1, $mailbox, $imapClient, $userId, $imapMessageFetcher1],
[3, $mailbox, $imapClient, $userId, $imapMessageFetcher2],
]);
$imapMessageFetcher1->expects(self::once())
->method('withBody')
->with($loadBody)
->willReturnSelf();
$imapMessageFetcher1->expects(self::once())
->method('fetchMessage')
->with($fetchResult1)
->willReturn($message1);
$imapMessageFetcher2->expects(self::once())
->method('withBody')
->with($loadBody)
->willReturnSelf();
$imapMessageFetcher2->expects(self::once())
->method('fetchMessage')
->with($fetchResult2)
->willReturn($message2);
$message1 = new IMAPMessage($imapClient, $mailbox, 1, $fetchResult1);
$message2 = new IMAPMessage($imapClient, $mailbox, 3, $fetchResult2);
$expected = [
$message1,
$message2,
];
$result = $this->mapper->findByIds($imapClient, $mailbox, new Horde_Imap_Client_Ids($ids));
$result = $this->mapper->findByIds(
$imapClient,
$mailbox,
new Horde_Imap_Client_Ids($ids),
$userId,
$loadBody
);
$this->assertEquals($expected, $result);
}
@ -99,6 +147,11 @@ class MessageMapperTest extends TestCase {
$imapClient = $this->createMock(Horde_Imap_Client_Socket::class);
$mailbox = 'inbox';
$ids = [1, 3];
$userId = 'user';
$loadBody = false;
$imapMessageFetcher1 = $this->createMock(ImapMessageFetcher::class);
$message1 = $this->createMock(IMAPMessage::class);
$fetchResults = new Horde_Imap_Client_Fetch_Results();
$fetchResult1 = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
@ -120,13 +173,32 @@ class MessageMapperTest extends TestCase {
->willReturn(1);
$fetchResult2->expects(self::never())
->method('getUid');
$this->imapMessageFactory->expects(self::once())
->method('build')
->willReturnMap([
[1, $mailbox, $imapClient, $userId, $imapMessageFetcher1],
]);
$imapMessageFetcher1->expects(self::once())
->method('withBody')
->with($loadBody)
->willReturnSelf();
$imapMessageFetcher1->expects(self::once())
->method('fetchMessage')
->with($fetchResult1)
->willReturn($message1);
$message1 = new IMAPMessage($imapClient, $mailbox, 1, $fetchResult1);
$expected = [
$message1
];
$result = $this->mapper->findByIds($imapClient, $mailbox, new Horde_Imap_Client_Ids($ids));
$result = $this->mapper->findByIds(
$imapClient,
$mailbox,
new Horde_Imap_Client_Ids($ids),
$userId,
$loadBody
);
$this->assertEquals($expected, $result);
}
@ -162,7 +234,8 @@ class MessageMapperTest extends TestCase {
5000,
0,
$this->createMock(LoggerInterface::class),
$this->createMock(PerformanceLoggerTask::class)
$this->createMock(PerformanceLoggerTask::class),
'user'
);
$this->assertSame(
@ -233,7 +306,8 @@ class MessageMapperTest extends TestCase {
5000,
0,
$this->createMock(LoggerInterface::class),
$this->createMock(PerformanceLoggerTask::class)
$this->createMock(PerformanceLoggerTask::class),
'user'
);
self::assertTrue($result['all']);
@ -297,7 +371,8 @@ class MessageMapperTest extends TestCase {
5000,
300,
$this->createMock(LoggerInterface::class),
$this->createMock(PerformanceLoggerTask::class)
$this->createMock(PerformanceLoggerTask::class),
'user'
);
self::assertTrue($result['all']);
@ -368,7 +443,8 @@ class MessageMapperTest extends TestCase {
5000,
92000,
$this->createMock(LoggerInterface::class),
$this->createMock(PerformanceLoggerTask::class)
$this->createMock(PerformanceLoggerTask::class),
'user'
);
// This chunk returns 8k messages, when we only expected 5k. So the process
@ -409,7 +485,8 @@ class MessageMapperTest extends TestCase {
5000,
99999,
$this->createMock(LoggerInterface::class),
$this->createMock(PerformanceLoggerTask::class)
$this->createMock(PerformanceLoggerTask::class),
'user'
);
self::assertTrue($result['all']);

Просмотреть файл

@ -4,6 +4,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -87,6 +88,7 @@ class SynchronizerTest extends TestCase {
$response = $this->synchronizer->sync(
$imapClient,
$request,
'user',
Horde_Imap_Client::SYNC_VANISHEDUIDS
);
@ -125,6 +127,7 @@ class SynchronizerTest extends TestCase {
$response = $this->synchronizer->sync(
$imapClient,
$request,
'user',
Horde_Imap_Client::SYNC_VANISHEDUIDS
);

Просмотреть файл

@ -3,6 +3,7 @@
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -23,71 +24,136 @@
namespace OCA\Mail\Tests\Unit\Model;
use ChristophWurst\Nextcloud\Testing\TestCase;
use Horde_Imap_Client;
use Horde_Imap_Client_Data_Fetch;
use Horde_Imap_Client_Fetch_Results;
use Horde_Imap_Client_DateTime;
use Horde_Mime_Part;
use OCA\Mail\AddressList;
use OCA\Mail\Db\Tag;
use OCA\Mail\Model\IMAPMessage;
use OCA\Mail\Service\Html;
use OCP\IURLGenerator;
use OCP\IRequest;
class IMAPMessageTest extends TestCase {
public function testNoFrom() {
$data = new Horde_Imap_Client_Data_Fetch();
$m = new IMAPMessage(null, 'INBOX', 123, $data);
/** @var Html|MockObject */
private $htmlService;
$this->assertEquals(new AddressList(), $m->getFrom());
protected function setUp(): void {
$this->htmlService = $this->createMock(Html::class);
}
public function testIconvHtmlMessage() {
$conn = $this->getMockBuilder('Horde_Imap_Client_Socket')
->disableOriginalConstructor()
->setMethods(['fetch'])
->getMock();
$urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator')
$urlGenerator = $this->getMockBuilder(IURLGenerator::class)
->disableOriginalConstructor()
->getMock();
$request = $this->getMockBuilder('\OCP\IRequest')
->disableOriginalConstructor()
->getMock();
//linkToRoute 'mail.proxy.proxy'
$urlGenerator->expects($this->any())
->method('linkToRoute')
->will($this->returnCallback(function ($url) {
return "https://docs.example.com/server/go.php?to=$url";
}));
$request = $this->getMockBuilder(IRequest::class)
->disableOriginalConstructor()
->getMock();
$htmlService = new Html($urlGenerator, $request);
// mock first fetch
$firstFetch = new Horde_Imap_Client_Data_Fetch();
$firstPart = Horde_Mime_Part::parseMessage(file_get_contents(__DIR__ . '/../../data/mail-message-123.txt'),
$part = Horde_Mime_Part::parseMessage(file_get_contents(__DIR__ . '/../../data/mail-message-123.txt'),
['level' => 1]);
$firstFetch->setStructure($firstPart);
$firstFetch->setBodyPart(1, $firstPart->getPart(1)->getContents());
$firstFetch->setBodyPart(2, $firstPart->getPart(2)->getContents());
$firstResult = new Horde_Imap_Client_Fetch_Results();
$firstResult[123] = $firstFetch;
$conn->expects($this->any())
->method('fetch')
->willReturn($firstResult);
$plainTextBody = $part[$part->findBody('plain')]->getContents();
$htmlBody = $part[$part->findBody('html')]->getContents();
$inlineAttachmentPart = $part->getPart(2);
$message = new IMAPMessage(
1234,
'<1576747741.9.1432038946316.JavaMail.root@ip-172-32-11-10>',
[],
AddressList::parse('from@mail.com'),
AddressList::parse('to@mail.com'),
AddressList::parse('cc@mail.com'),
AddressList::parse('bcc@mail.com'),
AddressList::parse('reply-to@mail.com'),
'core/master has new results',
$plainTextBody,
$htmlBody,
true,
[],
[],
false,
[],
new Horde_Imap_Client_DateTime('2016-01-01 00:00:00'),
'',
'disposition',
'',
false,
false,
false,
$htmlService,
);
$message = new IMAPMessage($conn, 'INBOX', 123, null, true, $htmlService);
$htmlBody = $message->getHtmlBody(123);
$this->assertTrue(strlen($htmlBody) > 1000);
$actualHtmlBody = $message->getHtmlBody(123);
$this->assertTrue(strlen($actualHtmlBody) > 1000);
$plainTextBody = $message->getPlainBody();
$this->assertTrue(strlen($plainTextBody) > 1000);
$actualPlainTextBody = $message->getPlainBody();
$this->assertEquals($plainTextBody, $actualPlainTextBody);
}
public function testSerialize() {
$data = new Horde_Imap_Client_Data_Fetch();
$data->setUid(1234);
$m = new IMAPMessage(null, 'INBOX', 123, $data);
$m = new IMAPMessage(
1234,
'foo',
[ Horde_Imap_Client::FLAG_SEEN, Tag::LABEL_IMPORTANT ],
AddressList::parse('from@mail.com'),
AddressList::parse('to@mail.com'),
AddressList::parse('cc@mail.com'),
AddressList::parse('bcc@mail.com'),
AddressList::parse('reply-to@mail.com'),
'subject',
'',
'',
true,
[],
[],
false,
[],
new Horde_Imap_Client_DateTime('2016-01-01 00:00:00'),
'',
'disposition',
'',
false,
false,
false,
$this->htmlService,
);
$json = $m->jsonSerialize();
$this->assertEquals([
'uid' => 1234,
'messageId' => 'foo',
'from' => [ [ 'label' => 'from@mail.com', 'email' => 'from@mail.com' ] ],
'to' => [ [ 'label' => 'to@mail.com', 'email' => 'to@mail.com' ] ],
'cc' => [ [ 'label' => 'cc@mail.com', 'email' => 'cc@mail.com' ] ],
'bcc' => [ [ 'label' => 'bcc@mail.com', 'email' => 'bcc@mail.com' ] ],
'subject' => 'subject',
'dateInt' => 1451606400,
'flags' => [
'seen' => true,
'flagged' => false,
'answered' => false,
'deleted' => false,
'draft' => false,
'forwarded' => false,
'hasAttachments' => false,
'mdnsent' => false,
'important' => true,
],
'hasHtmlBody' => true,
'dispositionNotificationTo' => 'disposition',
'scheduling' => [],
], $json);
$this->assertEquals(1234, $json['uid']);
}
}

Просмотреть файл

@ -2,6 +2,7 @@
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -327,7 +328,7 @@ class AttachmentServiceTest extends TestCase {
->willReturn($mailbox);
$this->messageMapper->expects(self::once())
->method('getFullText')
->with($client, $mailbox->getName(), $message->getUid())
->with($client, $mailbox->getName(), $message->getUid(), $userId)
->willReturn('sjdhfkjsdhfkjsdhfkjdshfjhdskfjhds');
$this->mapper->expects($this->once())
->method('insert')

Просмотреть файл

@ -4,6 +4,7 @@ declare(strict_types=1);
/**
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* Mail
*
@ -517,6 +518,9 @@ class MailManagerTest extends TestCase {
public function testGetMailAttachments(): void {
$account = $this->createMock(Account::class);
$account->expects($this->once())
->method('getUserId')
->willReturn('user');
$attachments = [
[
'content' => 'abcdefg',

Просмотреть файл

@ -184,8 +184,10 @@ class MailTransmissionTest extends TestCase {
}
public function testSendNewMessageWithMessageAsAttachment() {
$userId = 'testuser';
$mailAccount = new MailAccount();
$mailAccount->setUserId('testuser');
$mailAccount->setUserId($userId);
$mailAccount->setSentMailboxId(123);
/** @var Account|MockObject $account */
@ -193,6 +195,7 @@ class MailTransmissionTest extends TestCase {
$account->method('getMailAccount')->willReturn($mailAccount);
$account->method('getName')->willReturn('Test User');
$account->method('getEMailAddress')->willReturn('test@user');
$account->method('getUserId')->willReturn($userId);
$originalAttachment = [
[
@ -244,7 +247,8 @@ class MailTransmissionTest extends TestCase {
->with(
$this->imapClientFactory->getClient($account),
$mailbox->getName(),
11
11,
$userId,
)
->willReturn($source);
@ -309,7 +313,7 @@ class MailTransmissionTest extends TestCase {
$content = ['blablabla'];
$this->messageMapper->expects($this->once())
->method('getRawAttachments')
->with($this->imapClientFactory->getClient($account), $mailbox->getName(), $attachmentMessage->getUid(),
->with($this->imapClientFactory->getClient($account), $mailbox->getName(), $attachmentMessage->getUid(), 'testuser',
[$originalAttachment[0]['id']])
->willReturn($content);

Просмотреть файл

@ -0,0 +1,304 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
*
* @author Richard Steinmetz <richard@steinmetz.cloud>
*
* @license AGPL-3.0-or-later
*
* 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, or
* (at your option) any later version.
*
* 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 OCA\Mail\Tests\Service;
use ChristophWurst\Nextcloud\Testing\TestCase;
use Horde_Imap_Client_Data_Envelope;
use Horde_Imap_Client_Data_Fetch;
use Horde_Mime_Headers;
use Horde_Mime_Headers_ContentParam_ContentType;
use OCA\Mail\AddressList;
use OCA\Mail\Db\SmimeCertificate;
use OCA\Mail\Db\SmimeCertificateMapper;
use OCA\Mail\Service\SmimeService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ICertificateManager;
use OCP\ITempManager;
use OCP\Security\ICrypto;
use PHPUnit\Framework\MockObject\MockObject;
class SmimeServiceTest extends TestCase {
private $tempFiles = [];
/** @var ITempManager|MockObject */
private $tempManager;
/** @var ICertificateManager|MockObject */
private $certificateManager;
/** @var ICrypto|MockObject */
private $crypto;
/** @var SmimeCertificateMapper|MockObject */
private $certificateMapper;
/** @var ITimeFactory|MockObject */
private $timeFactory;
protected function setUp(): void {
parent::setUp();
$this->tempManager = $this->createMock(ITempManager::class);
$this->certificateManager = $this->createMock(ICertificateManager::class);
$this->crypto = $this->createMock(ICrypto::class);
$this->certificateMapper = $this->createMock(SmimeCertificateMapper::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->smimeService = new SmimeService(
$this->tempManager,
$this->certificateManager,
$this->crypto,
$this->certificateMapper,
$this->timeFactory
);
}
protected function tearDown(): void {
parent::tearDown();
foreach ($this->tempFiles as $tempFile) {
unlink($tempFile);
}
$this->tempFiles = [];
}
private function getTestCertificate(string $emailAddress, string $userId = 'user'): SmimeCertificate {
$rawCert = file_get_contents(__DIR__ . "/../../data/smime-certs/{$emailAddress}.crt");
$rawKey = file_get_contents(__DIR__ . "/../../data/smime-certs/{$emailAddress}.key");
$certificate = new SmimeCertificate();
$certificate->setId(42);
$certificate->setUserId($userId);
$certificate->setEmailAddress($emailAddress);
$certificate->setCertificate($rawCert);
$certificate->setPrivateKey($rawKey);
return $certificate;
}
private function createTempFile(): string {
$n = count($this->tempFiles);
$tempFile = "/tmp/mail-smime-service-temp-{$n}";
touch($tempFile);
$this->tempFiles[] = $tempFile;
return $tempFile;
}
public function testDecryptMimePartText() {
$encryptedMessage = file_get_contents(__DIR__ . '/../../data/encrypted-message.txt');
$decryptedBody = file_get_contents(__DIR__ . '/../../data/decrypted-message-body.txt');
$certificate = $this->getTestCertificate('user@imap.localhost');
$this->crypto->expects(self::exactly(2))
->method('decrypt')
->willReturnMap([
[$certificate->getCertificate(), '', $certificate->getCertificate()],
[$certificate->getPrivateKey(), '', $certificate->getPrivateKey()],
]);
$this->tempManager->expects(self::exactly(2))
->method('getTemporaryFile')
->willReturnOnConsecutiveCalls($this->createTempFile(), $this->createTempFile());
$this->assertEquals(
$decryptedBody,
$this->smimeService->decryptMimePartText($encryptedMessage, $certificate),
);
}
public function testDecryptDataFetch(): void {
$encryptedMessage = file_get_contents(__DIR__ . '/../../data/encrypted-message.txt');
$decryptedBody = file_get_contents(__DIR__ . '/../../data/decrypted-message-body.txt');
$message = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
$message->expects(self::once())
->method('getFullMsg')
->willReturn($encryptedMessage);
$headers = new Horde_Mime_Headers();
$contentType = new Horde_Mime_Headers_ContentParam_ContentType('', 'application/pkcs7-mime');
$contentType['smime-type'] = 'enveloped-data';
$headers['content-type'] = $contentType;
$message->expects(self::once())
->method('getHeaderText')
->with('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
->willReturn($headers);
$envelope = new Horde_Imap_Client_Data_Envelope();
$envelope->to = AddressList::parse('user@imap.localhost')->toHorde();
$message->expects(self::once())
->method('getEnvelope')
->willReturn($envelope);
$certificate = $this->getTestCertificate('user@imap.localhost');
$this->certificateMapper->expects(self::once())
->method('findAllByEmailAddress')
->with('user', 'user@imap.localhost')
->willReturn([$certificate]);
$this->crypto->expects(self::exactly(2))
->method('decrypt')
->willReturnMap([
[$certificate->getCertificate(), '', $certificate->getCertificate()],
[$certificate->getPrivateKey(), '', $certificate->getPrivateKey()],
]);
$this->tempManager->expects(self::exactly(2))
->method('getTemporaryFile')
->willReturnOnConsecutiveCalls($this->createTempFile(), $this->createTempFile());
$this->assertEquals(
$decryptedBody,
$this->smimeService->decryptDataFetch($message, 'user'),
);
}
public function testDecryptDataFetchWithOpaqueSignedData(): void {
$encryptedMessage = file_get_contents(__DIR__ . '/../../data/encrypted-signed-opaque-message.txt');
$decryptedBody = file_get_contents(__DIR__ . '/../../data/decrypted-signed-opaque-message-body.txt');
$message = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
$message->expects(self::once())
->method('getFullMsg')
->willReturn($encryptedMessage);
$headers = new Horde_Mime_Headers();
$contentType = new Horde_Mime_Headers_ContentParam_ContentType('', 'application/pkcs7-mime');
$contentType['smime-type'] = 'enveloped-data';
$headers['content-type'] = $contentType;
$message->expects(self::once())
->method('getHeaderText')
->with('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
->willReturn($headers);
$envelope = new Horde_Imap_Client_Data_Envelope();
$envelope->to = AddressList::parse('user@imap.localhost')->toHorde();
$message->expects(self::once())
->method('getEnvelope')
->willReturn($envelope);
$certificate = $this->getTestCertificate('user@domain.tld');
$this->certificateMapper->expects(self::once())
->method('findAllByEmailAddress')
->with('user', 'user@imap.localhost')
->willReturn([$certificate]);
$this->crypto->expects(self::exactly(2))
->method('decrypt')
->willReturnMap([
[$certificate->getCertificate(), '', $certificate->getCertificate()],
[$certificate->getPrivateKey(), '', $certificate->getPrivateKey()],
]);
$this->tempManager->expects(self::exactly(4))
->method('getTemporaryFile')
->willReturnOnConsecutiveCalls(
$this->createTempFile(),
$this->createTempFile(),
$this->createTempFile(),
$this->createTempFile(),
);
$this->certificateManager->expects(self::once())
->method('getAbsoluteBundlePath')
->willReturn(__DIR__ . '/../../data/smime-certs/domain.tld.ca.crt');
$this->assertEquals(
$decryptedBody,
$this->smimeService->decryptDataFetch($message, 'user'),
);
}
public function testDecryptDataFetchWithRegularMessage(): void {
$messageText = file_get_contents(__DIR__ . '/../../data/mail-message-123.txt');
$message = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
$message->expects(self::once())
->method('getFullMsg')
->willReturn($messageText);
$headers = new Horde_Mime_Headers();
$headers['content-type'] = new Horde_Mime_Headers_ContentParam_ContentType('', 'multipart/alternative');
$message->expects(self::once())
->method('getHeaderText')
->with('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
->willReturn($headers);
$this->assertEquals(
$messageText,
$this->smimeService->decryptDataFetch($message, 'user'),
);
}
public function provideIsEncryptedData(): array {
return [
['application/pkcs7-mime', ['smime-type' => 'enveloped-data'], true],
['application/pkcs7-mime', ['smime-type' => 'signed-data'], false],
['application/pkcs7-mime', [], false], // Should not happen in real life but who knows
['multipart/alternative', [], false],
['', [], false],
[null, [], false],
];
}
/**
* @dataProvider provideIsEncryptedData
*/
public function testIsEncrypted(?string $contentType,
array $contentTypeParams,
bool $expectedResult): void {
$message = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
$headers = new Horde_Mime_Headers();
$contentType = new Horde_Mime_Headers_ContentParam_ContentType('', $contentType);
foreach ($contentTypeParams as $key => $value) {
$contentType[$key] = $value;
}
$headers['content-type'] = $contentType;
$message->expects(self::once())
->method('getHeaderText')
->with('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
->willReturn($headers);
$this->assertEquals($expectedResult, $this->smimeService->isEncrypted($message));
}
public function testIsEncryptedWhenHeaderIsMissing(): void {
$message = $this->createMock(Horde_Imap_Client_Data_Fetch::class);
$headers = new Horde_Mime_Headers();
$message->expects(self::once())
->method('getHeaderText')
->with('0', Horde_Imap_Client_Data_Fetch::HEADER_PARSE)
->willReturn($headers);
$this->assertFalse($this->smimeService->isEncrypted($message));
}
public function testExtractSignedContent(): void {
$signedMessage = file_get_contents(__DIR__ . '/../../data/signed-opaque-message.txt');
$verifiedContent = file_get_contents(__DIR__ . '/../../data/decrypted-signed-opaque-message-body.txt');
$this->tempManager->expects(self::exactly(2))
->method('getTemporaryFile')
->willReturnOnConsecutiveCalls(
$this->createTempFile(),
$this->createTempFile(),
);
$this->certificateManager->expects(self::once())
->method('getAbsoluteBundlePath')
->willReturn(__DIR__ . '/../../data/smime-certs/domain.tld.ca.crt');
$this->assertEquals(
$verifiedContent,
$this->smimeService->extractSignedContent($signedMessage),
);
}
}

Просмотреть файл

@ -0,0 +1,347 @@
Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-512; boundary="------------ms070102030302080409090706"
--------------ms070102030302080409090706
Content-Type: multipart/mixed; boundary="------------WSu0joeUl4a7h0IHpwEDRFqQ"
--------------WSu0joeUl4a7h0IHpwEDRFqQ
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Just some encrypted test images.
--------------WSu0joeUl4a7h0IHpwEDRFqQ
Content-Type: image/png;
name="img_800x600_3x8bit_RGB_grid_triangle_a_0200x0150_0.png"
Content-Disposition: attachment;
filename="img_800x600_3x8bit_RGB_grid_triangle_a_0200x0150_0.png"
Content-Transfer-Encoding: base64
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
--------------WSu0joeUl4a7h0IHpwEDRFqQ
Content-Type: image/png;
name="img_800x600_3x8bit_RGB_grid_rectangle_0200x0150_0.png"
Content-Disposition: attachment;
filename="img_800x600_3x8bit_RGB_grid_rectangle_0200x0150_0.png"
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAIAAAAVFBUnAAARSklEQVR42u3VwQkAAAgDse6/
tO4g9CPJCELPDBQECiwLvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQ
LAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9AstCsBAs
9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/Q
KwQLBAu9Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQKvQLBQrDQK9ArBAvBAr1CrxAs
ECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQ
K9ArBAvBAr1CrxAsECz0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FCr0Cv
ECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrAQ
LNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv
0CsQLAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECz0CvQKwUKwQK/QKwQLBAu9Qq9A
sBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKw
QK/QKwQLBAu9Qq9AsBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvBAr1C
rxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvUKw
QLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FC
r0CvECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsBAs0Cv0CsECwUKv0CsQLAQLvQK9
QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsEC
wUKv0CsQLAQLvQK9QrAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/QKwQLBAu9
Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0Cr0C
wUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvB
Ar1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9QrBAsNAr9AoEC8FCr0CvECwEC/QK
vUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoE
C8FCr0CvECwEC/QKvUKwECzQK/QKwQLBQq/QKwCPEI8Q9Aq9QrBAsNAr9AoEC8FCr0CvECwE
C/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrAQLNAr
9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQ
LAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECz0CvQKwUKwQK/QKwQLBAu9Qq9AsBAs
9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/Q
KwQLBAu9Qq9AsBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvBAr1CrxAs
ECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvUKwQLDQ
K/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FCr0Cv
ECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsBAs0Cv0CsECwUKv0CsQLAQLvQK9QrAQ
LNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv
0CsQLAQLvQK9QrAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/QKwQLBAu9Qq9A
sBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0Cr0CwUKw
0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvBAr1C
rxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AtNCsNAr9AoEC8FCr0CvECwEC/QKvUKw
QLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FC
r0CvECwEC/QKvUKwwLLQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQLAQLvQK9
QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAssCz0CsFC
sECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/QKwQLBAu9
Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQKLAvBQrDQK9ArBAvBAr1CrxAsECz0Cr0C
wUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvB
Ar1CrxAsECz0Cr0CwUKw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FCr+DWKyfAI8Qj
RK9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQK
wUKwQK/QKwQLBAu9Qq9AsBAs9Ar0CsFCsECv0CsECywLvUKvECwQLPQKvQLBQrDQK9ArBAvB
Ar1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQK
vQLBQrDQK9ArBAvBAstCrxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoE
C8FCr0CvECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvwLIQLAQL
vQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0
CsECwUKv0CsQLAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC71Cr0CwECz0CvQKwUKwQK/QKwQL
BAu9Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0
CvQKwUKwQK/QKwQLBAu9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9Ar
BAvBAr1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwE
C/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr
9AoEC8FCr0CvECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQ
LAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs
0Cv0CsECwUKv0CsQLAQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/Q
KwQLBAu9Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71CrxAs
ECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQ
K9ArBAvBAr1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLAQL9Aq9QrBAsNAr9AoEC8FCr0Cv
ECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBA
sNAr9AoEC8FCr0CvECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv
0CsQLAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Qq9A
sBAs9Ar0CsFCsECv0CsAjxCPEL0CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsEC
wUKv0CsQLAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC71Cr0CwECz0CvQKwUKwQK/QKwQLBAu9
Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQK
wUKwQK/QKwQLBAu9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9ArBAvB
Ar1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwEC/QK
vUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoE
C8FCr0CvECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQLAQL
vQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0
CsECwUKv0CsQLAQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/QKwQL
BAu9Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71CrxAsECz0
Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQK9Ar
BAvBAr1CrxAsECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLAQL9Aq9QrBAsNAr9AoEC8FCr0CvECwE
C/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr
9AoEC8FCr0CvECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Ar1CsBAs0Cv0CsECwUKv0CsQ
LAQLvQK9QrAQLNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq/QKxAsBAu9Qq9AsBAs
9Ar0CsFCsECv0CsECwQLvUKvQLAQLPQK9ArBQrBAr9ArBAsEC71Cr0CwECz0CvQKwUKwQK/Q
KwQLBAu9Qq9AsBAs9Ar0CsFCsECv0CsECwQLvUKvwLQQLPQKvQLBQrDQK9ArBAvBAr1CrxAs
ECz0Cr0CwUKw0CvQKwQLwQK9Qq8QLBAs9Aq9AsFCsNAr0CsEC8ECvUKvECwQLPQKvQLBQrDQ
K9ArBAvBAr1CrxAssCz0Cr1CsECw0Cv0CgQLwUKvQK8QLAQL9Aq9QrBAsNAr9AoEC8FCr0Cv
ECwEC/QKvUKwQLDQK/QKBAvBQq9ArxAsBAv0Cr1CsECw0Cv0CgQLwUKvQK8QLAQLLAu9QrAQ
LNAr9ArBAsFCr9ArECwEC70CvUKwECzQK/QKwQLBQq94bQEqwyevAF3wnwAAAABJRU5ErkJg
gg==
--------------WSu0joeUl4a7h0IHpwEDRFqQ
Content-Type: image/png;
name="img_800x600_3x8bit_RGB_rectangles_concentric_0256_1.png"
Content-Disposition: attachment;
filename="img_800x600_3x8bit_RGB_rectangles_concentric_0256_1.png"
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAIAAAAVFBUnAAAKNElEQVR42u3WQQ0AAAwCMfyb
3lTwIGlFwCUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYw4ACjwsAgsABBYILAAEFggs
AAQWCCwAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACAIEFAgsAgQUCCwCBBQILAIEF
AgsABBYILAAEFggsAAQWCCwAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACAIEFAgsA
gQUCCwCBBQILAIEFAgsABBYILAAEFggsAAQWCCwAEFggsAAQWCCwABBYILAAQGCBwAJAYIHA
AkBggcACAIEFAgsAgQUCCwCBBQILAIEFAgsABBYILAAEFggsAAQWCCwAEFggsAAQWCCwABBY
ILAAQGCBwAJAYIHAAkBggcACAIGFwAIAgQUCCwCBBQILAIEFAgsABBYILAAEFggsAAQWCCwA
EFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACQGCBwAIAgQUCCwCBBQILAIEFAgsABBYI
LAAEFggsAAQWCCwAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACQGCBwAIAgQUCCwCB
BQILAIEFAgsABBYILAAEFggsAAQWCCwAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcAC
QGCBwAIAgQUCCwCBBQILAIEFAgsABBYILAAEFggsAAQWCCwAEFggsAAQWCCwABBYILAAQGAh
sABAYIHAAkBggcACQGCBwAIAgQUCCwCBBQILAIEFAgsABBYILAAEFggsAAQWCCwAEFggsAAQ
WCCwABBYILAAEFggsABAYIHAAkBggcACQGCBwAIAgQUCCwCBBQILAIEFAgsABBYILAAEFggs
AAQWCCwAEFggsAAQWCCwABBYILAAEFggsABAYIHAAkBggcACQGCBwMIAgX3DvoEBMkCAfbNv
YIAMEGDf7BsYIAwQ2DfsGxggAwT2DfsGBsgAAfbNvoEBwgCBfcO+gQEyQGDfsG9ggAwQYN/s
GxggDBDYN/sGBggDBPYN+wYGyAAB9s2+gQHCAIF9s29ggDBAYN+wb2CADBDYN+wbGCADBNg3
+wYGCAME9g37BgbIAIF9w76BATJAgH2zb2CAMEBg37BvGCAMENg37BsYIAME2Df7BgYIAwT2
zb6BAcIAgX3DvoEBMkCAfbNvYIAwQGDf7BsYIAwQ2DfsGxggAwT2DfsGBsgAAfbNvoEBwgCB
fcO+gQEyQGDfsG9ggAwQYN/sGxggDBDYN/sGBggDBPYN+wYGyAAB9s2+gQHCAIF9s29ggDBA
YN+wb2CADBBg3+wbGCADBNg3+wYGCAME9g37BgbIAIF9w76BATJAgH2zb2CAMEBg37BvYIAM
ENg37BsYIAME2Df7BgYIAwT2zb6BAcIAgX3DvoEBMkCAfbNvYIAwQGDf7BsYIAwQ2DfsGxgg
AwTYN/sGBsgAAfbNvoEBwgCBfcO+gQEyQGDfsG9ggAwQYN/sGxggDBDYN+wbGCADBPYN+wYG
yAAB9s2+gQHCAIF9s29ggDBAYN+wb2CADBBg3+wbGCAMENg3+wYGCAME9g37BgbIAIF9w76B
ATJAgH2zb2CAMEBg37BvYIAMENg37BsYIAME2Df7BgYIAwT2DfuGAcIAgX3DvoEBMkCAfbNv
YIAwQGDf7BsYIAwQ2DfsGxggAwTYN/sGBggDBPbNvoEBwgCBfcO+gQEyQGDfsG9ggAwQYN/s
GxggDBDYN+wbGCADBPYN+wYGyAAB9s2+gQHCAIF9s29ggDBAYN+wb2CADBBg3+wbGCAMENg3
+wYGCAME9g37BgbIAAH2zb6BATJAgH2zb2CAAEBggcACQGCBwAJAYIHAAgCBBQILAIEFAgsA
gQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYILAAEFggsAAQWCCwAEBggcACQGCBwAJAYIHA
AgCBBQILAIEFAgsAgQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYILAAEFggsAAQWCCwAEBg
gcACQGCBwAJAYIHAAgCBBSYAAIEFAgsAgQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYILAA
EFggsAAQWCCwAEBggcACQGCBwAJAYIHAAgAPi8ACAIEFAgsAgQUCCwCBBQILAAQWCCwABBYI
LAAEFggsABBYILAAEFggsAAQWCCwAEBggcACQGCBwAJAYIHAAkBggcACAIEFAgsAgQUCCwCB
BQILAAQWCCwABBYILAAEFggsABBYILAAEFggsAAQWCCwAEBggcACQGCBwAJAYIHAAkBggcAC
AIEFAgsAgQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYILAAEFggsAAQWCCwAEBggcACQGCB
wAJAYIHAAkBggcACAIEFAgsAgQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYILAAEFggsAAQ
WCCwAEBgIbAAQGCBwAJAYIHAAkBggcACAIEFAgsAgQUCCwCBBQILAAQWCCwABBYILAAEFggs
ABBYILAAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACAIEFAgsAgQUCCwCBBQILAAQW
CCwABBYILAAEFggsABBYILAAEFggsAAQWCCwABBYILAAQGCBwAJAYIHAAkBggcACAIEFAgsA
gQUCCwCBBQILAAQWCCwABBYILAAEFggsABBYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
wKgHu00GsAtO32UAAAAASUVORK5CYII=
--------------WSu0joeUl4a7h0IHpwEDRFqQ--
--------------ms070102030302080409090706
Content-Type: application/pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature
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
GZAXp353gKS28x5OI/2RAAAAAAAA
--------------ms070102030302080409090706--

Просмотреть файл

@ -0,0 +1,4 @@
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable
hoi

Просмотреть файл

@ -0,0 +1,489 @@
Return-Path: <debug@imap.localhost>
Delivered-To: user@imap.localhost
Received: from 887788ce658a
by 887788ce658a (Dovecot) with LMTP id A5UpDBK97GOHCQAAq9CxcQ
for <user@imap.localhost>; Wed, 15 Feb 2023 11:08:02 +0000
Received: from [127.0.0.1] (887788ce658a [10.0.2.100])
by 887788ce658a (Postfix) with ESMTP id 2C9691D33
for <user@imap.localhost>; Wed, 15 Feb 2023 11:08:02 +0000 (UTC)
Message-ID: <98eec0af-2074-685b-7bd8-ac0a06e8996f@imap.localhost>
Date: Wed, 15 Feb 2023 12:08:02 +0100
MIME-Version: 1.0
User-Agent: redacted
Content-Language: en-US
To: user@imap.localhost
From: admin <debug@imap.localhost>
Subject: Encrypted test images
Content-Type: application/pkcs7-mime; smime-type="enveloped-data"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7m"
Content-Description: S/MIME Encrypted Message
MIAGCSqGSIb3DQEHA6CAMIACAQAxggSIMIICQAIBADAoMBQxEjAQBgNVBAMMCVMvTUlNRSBD
QQIQJ1gWlwBuc4if0oHwyYhA5zANBgkqhkiG9w0BAQEFAASCAgAHATO/MlP16i5pHaP9KrB6
FMva5Cp7wNz/CtJR32LP9iRddgJAcCnFjbjN5Xk6soy90YsKWacDBQllhDjpT/LNy9WgBUli
Jccfk+zRUsxZZbvN5CL8YnewM6tcwe3su5vWXKlZwPoei8CN7PVHym4Up8L1Oz0y+T5dHO4y
yJnuZH74qItVFd3yd6R/Vj4Dm0WrMbjrUhY8AAIwi8jKZkbQ1xMbVgOl+SKwqeDtrIQ7BgVK
58O/2QNl9X6mjuEmaWj2Ty2zTDe9RnyLwrmkgt8wJ4nWRiYfaBmwODZh/6HbSbYiGPWZSuEE
I5aE81iJSo9ygUHetcOb7ncM/LcWyl3+zw1LAK42r0EWcgCfjYnDgB6zzyEF+/BnLktS/URh
M3TtjXcYgGGQQcjv/lErGj0qIV9/aeKgV5VcSrl+fdqExZqAWw8Pm0bYSEKLiI4gKSht4Ey9
GVDA+baUc9BiWyWt2XLpj9GLx0zk8ywMYsH6DFJ0Mqs4VBNAEwoYWL4oame9uPe36wVdWbuU
NjLj0ali8i+ePtEqHZRnac9Z1w2A1IkHSSUfdi2Egdf+yr5gz0qAHkvFUQpa8/F/nKB/WOfZ
hvCir+dbAdTrauf+MaMIM5ELVmKgnVp1nuvRQKVUfmdM07O3g+YmU4cQ2bnegp1pgsz7F+K7
lKaA0J8V0DbMvjCCAkACAQAwKDAUMRIwEAYDVQQDDAlTL01JTUUgQ0ECEDzUXrlMjPg1CGNI
yLPcF34wDQYJKoZIhvcNAQEBBQAEggIAUZnUgO1/Ok5J0i96XitACmxzr1KGnyb0FluGQfWK
YmZ8qKqCuVwEWnw+Hs8+VbU0cv86fgSMOSuvpwHYjIF8JJ5q98Uq2b/JxzwMsS6wKbsYE5F+
wZ7lvCVjxEjgeWpXoLWFbJ3Asd/RcMdQk0Fv7OucYGAIEWE3V2ZQftBgIVhhx9d+TEslyFx0
499EG7cYlihcc+bmjEeSLPcuuMi4EiogMvKzUTybPPpAyU/5q2Dk2HFFz1QPk91nryuEwlPW
7jQzCQ0+ZHhhDqdnz6GJDyp8Kag5Bm4+KjBB2VyZTPP9tWuiOL4tcH7PJka+RDKABZEi+eRv
1E3sWmFx/gqgpHrxlqCvk9sEGFKu3fHU9Op09mgrX9FMbAE7xBtFfcq1Y6xtCEmlTEWfA7AK
hifGq8wMRe8Jnq/lIZd4LWZWkLs8cXqk3+f+aE68v/R9x9SuWq8Tfrlk1PubfAoO0g6bE0+f
lpc/kAHid+12ccOkB54DO7bzpsbUo3hRWkNUgIG/CeKVbYLlhPgD4kskwGvJMWHpnWj4CkdO
8uSPypUfBt8MPifvSdufqimezFxaZyQ39V7wZ/4vkEzrlSNW9Y9+L6vFNRj0UZRm/Dv9+Rci
+ifQgcJ+kT+jVTr0824/ZFyh2Dx8Rzo6hdVr/EJNUoDzXrA06r9BI3wQSAWcIkAsYmwwgAYJ
KoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQTA2uDDHpZd08ar7nXcjjwaCABIIgAJnlpcqpQODZ
tL+BZMcDxHzkX9dF1Zq4hIFoWaz7SCjopJa0nNcNPL4RM33RWH+AawDKQdpW1oM0eMTjmVUI
tFBP9AiKMrbWr75iG7QqMPB514iKP70yXPXOYI1duvBfR6+HNFdjXa9svuv6T9SKSPjB1LQQ
4snFWiQ9Z7sFbxofQ4vldyNLp9gAEk5iBcOhDeFTxhgzBkqLm8CHA/M8gdDisZ7Z/LBXLv2p
bZ3DiW5O85hhzLuGDhabBECTWJrkNioxzGjVHtbTBQLvxYKKN8mAzrczkQPGYLMlRAbXcQpJ
9V4q2qdlsy6oBTy0WYl1oMvLTmBZm2yX9X7hxidERzuW1nvb+uFogVqfqcU0zshoNx8cLGe4
cjiUBnuFHXpVR/5SiqDZJbu5MAMQcrewkhPWNI/f2gfdn7FwKiFqOgg+tBeZ50181uNFlM/+
W/HXjtUa6YfRLhH/dyX1EzyiGZVE+DG7LJXwJWbyCgeoGP5JAKr5Mv2AaeD7hgKJ3mMBYuXh
IcV/tR4U5kNcT/h1jM8ZgYzXZ2fmVwZPx8FbLE/VoTpFHxL83EJUQB/MhLX4S8Jdx6mXFkW9
Oy7Ox5kskyNHu3UyIXtZMOH7/SRHT24vjcVERsE44ILwFx5QJph5L3mKHHJ4RQWwNZBLGCJX
VA65qZN1+kFUaCwj8J9rr9PCT7yeuWXEQ+Oc+XsWxi57W8WLvZvSK7misJe8yJelJ3i0wWOS
xVv5qZPK2KElftHhYGw5mRyXk/lHySl69BmSYGW9Q1o/0/DYrGT3rHjrd9lHmZxNksGYwpiG
FWm3jN0pYnYegM8XGwt/PHcMopWx9sNYsPcYS5dGEXmVCxs9hE3rkXMOeOOJUEAGz6lJ+r+y
ED5RlbDLLU+gh6pcAJZx76CeR4Tfm7gKc+skNENY3PQP0OIUDLCPOamosctBlgBup4+zojZI
VJQIH7cMvsdn55Y5uvIamX8jEbyYeZLh2IhzmtgMS+GpQQ7XnuNnBHSIcsVWOpu7Dv8SMDt9
h4D2A0eURv90m2Vv6nafB4xViLt38gBR5PTPjI0e2V0d1XubJlA0WTFlKLNDgGdq93aicGS6
aY+4hjFGlEHdtyvssc0fbQ0qF0ZKU4DpeVV57/R7RKxYP/gh5ht0KOd/R9f5KmooZey/JJ5f
fYSaeETGZCf1njGbsy3HcD1SvNdsfp/BNMZIewL7fXrSPyl9qSo0E+ossTvEvv1N6M6X5aFk
TtMUGD4NpxsYbT+ij13jCK4HACHBG+YPgEaCk+HdxKvBREKJMmYMka29VtEIK3RflEO1oM/n
6CTZPPWcPcxr2k4VdNz2JDF8U7o3DfKQ0C8jfiEVSuXCx04dnHJdUnN6U4R3jcQHBO+jwUCs
fMnVMdm7HWxEMWcMjAR9Z74zghqFbKc+sNoAzQimirGQFVSJDu4d5bh23mYfa88WVcuiWUSM
+4k7boW9aKsJS/iDWoZULaN+E2KSUtvqujq9lWHp05J7wE7/iQwdS3NgtiX+vOGZXRJScGjr
2uWxaCM0kPM1iWL63SGe+fsKI2zzYn8bXF1YADsE9YmuMiUMUKDiiuY4zcJMAWhXL+3oRwA7
VbU3dZ77Be85OBNp1eLvcRHIW8xwk4IkQW0Be/YTtrA+m7H+jCW39QDbP+sOodUcWxQ0aaWt
PSwYfpvXrZnLdfTtnp2w+ML+023Y3j4FUtHcclCV84qQk5QN0ezNK29ClGRQ+cobW5Z6mE9F
aAsxvriADmsOxRFoiimO9n5H0SbKsLlHDRfyTADvpo4S8tKyzmWNBBik75Lrm+z/bbH+WrMa
0d2VOaofeSAvzitttNKEprOxfmTPXaMal8h4GDO3+OrDDyXmW71/h1fL5IQCOgHBtFEvmYNi
sst9wvukOt3HIBGaeZqWgTnxq/6KpGsx5WCrqd4ueRktr/73cofRomhLVXswSgX8ikd2u8XQ
eEsEcwmV2TFGNafruMkwDw55T1Nj63JJ51gthjp64SxnT6dfQgaIUneMIYtcv5Gv5EjB/o0N
xJG5P0GtKIPwJdRFMIOn53c92AnzrXRbTcIq3ryWlWxEn1PNeqrQFqLFzpAQdkdqQ1WrvCrR
p+Ta93gj/X/cbXWP7l0LkdPBdq60oX/8uhrQATQ9sNua2F67j6K9OMNmTZD0jIv9G5iMuhYq
u0zdVgyCG51td1IDH0exXi29Xtde0bScnqob6FLq+ThegDL8Exm/O7wKAI0zt1Mi8W7JvQra
7IckUZApBlNUGIDtnylgUyVjSDVHKX7fP8tEffUtQBuKZgXBx0cc2h32BUhr1HVAltxuAI3Q
/jybPXZwmKbYAZlLWZtB0jb4dlDJ25tPIE/SmQ8iXsLFYvNy4/B1mU9Pvk1s9hJ8T2NqFkr0
IAqkfnyibxJS9n0FIu196XpXbMuBpfgn41rPwnjXlwlLBSBTdJXIxZmDs3U9NWiq+QUIc7zx
3eOHRD0mUR7KC6Rldq4kR87/VaN2aMLE2rrKcvKt54k108MmVsI4SaPsQAB4SsFxViT9iB57
awYIXm2OUKMo21X3Mt6v9T8gnjF48TEfO9bnkRb8KE1PL8A7g9WC52rfSkXXUpXudDZrEyhg
XYeUNmEzL83ZMnZgUKlK1wB5T9h5HP5vBYfGEl9NaaFVxygRn+dZygI9CJLgtvRxR9a6Zxtu
uiVPdZnIghag+ba3KTchEP3xgd8+EVcjviu6ZU5acNPaQ4ig6WXNbiaEMF+Z7l2njsqII1AG
lOxJNg/h7+g+ICqpuF78dEU+E+Ukv6S8epu+1ZXRIHEixCV6WPj4tAIxwEGoj5f02rX+u+Gz
5RDT+TfNBPd+sey5A8bDXM6Rba00bcg5xPLioAmPJPsetp7gyIuXF5+VjG1YFvEOPqEpseb7
SLqHucMaOkhjd3XCatB95tfFkJRDhlBLM2c2qjWhJfyCY09p+HNYXYEqm1BZTOLfyZcPMRl6
uEkw/Kek+afEcxLRfF6blN8FrfrouREp9O5PxIiDcywDgt7xlpM4K55REOauBc4Rq/WVHOfe
8VqiUNe/hyKRe/KWaIEVlJnPDoIOyqXwsC0rXnwd6iGW7t/8j+YJcjxH6rXOuf7Irj1JiWUP
NrD/KkYAx954Gs21k+qKSs0INXMr9FLzoluD1MEnwvG8PghrobeB+WuXoWjyy8n0XPR5CqS5
J9YJyO51Jd5EpzSvQzrMxXj04tirGIT9JV6cyPondpRHN0jFCe1nROTeHEKEie9GGmOVgmaM
qZ4PCcAtLdDR0pAGZofWo+4UGexzjsUAv6R+bHMhqrSLc00YOPl6lt/4hSqUqgK9+81Mphdr
rCUVaY1LRjFXlPkbWB1H6BN+kyTi7tbpTV9KLXNuP+xG+JYFWJH50ITxFCYU82vJvSENlCyD
7ENIYn7FO3mbhrFGiWPeiVNSNMpLLUPksgcrkOxWr5dYA86WPIPLiVy445oYR4nWWX2423r/
JRNaUtk25R4nc9yfotneYfTULPMQLJ9IJLCbvIrUFzVOOechFjlC2hnFo3d3j/sg87qshhsH
DOUV3Ix6h9LuSJx7UbohessDMJQUrYr/+yOQZIwzD2cLSZYvIEmtITpl3nuaeWJpyfB+XYBZ
gX2oxZJy4GjsBROiQlRQ3hDwaUnVxXsBrsalFj2NNDkAnxRNIBziYazB/wzhWafFeJmwjAaw
xVnpa+F1nyv5MNAtXRwtA0foRDBOycxrSppD/aXS/2DqnKuQLy2Sjok7Nwld/vKsCUOWZF0N
9tqnkPa5/OY5i7bU607MjjvK38QLoodkMUQfeSrKU3hBADfuHXBA0Pv1AXZY+b6enDE/SrRd
hElDkbC/r7517gHEf3CFxys8r17DSfR0X1Mj1rZ52NP/hySuP0E6GXT7QCZEykmvvbL1q0sv
xj9G8E6g3+V8edbUwWISSSvxQdQ6vYvx7/boiTduOAvW2cU3CIGNq2Wnu8KETgNOA8OgsG48
IEISvXeyJ8y1MeQoPVuFEbu6itvaGobpITod87FWdgD1+qeNljIwsn58E0VM0ycEw2PURUfh
oh+3A5Rn3H/KVqWXHNx2tKOZuSEaycqJPXA8nZf/HT+rFnaiUK3yzNAeDh3YfJe2oVtGWnfJ
TtOmnD+qPZ8A6cyN7EriXoAWVJXNLxhNEG8LMKQtYjUqlvwo4gaV4YuQZHW+cFqirgX3OTZj
FmZL50UN3kOT/LE9BsRX9X63Tr+tJKXVZbHDHcnCxOzBjXtMRGD6CxXVMZeU84c1mPi3MHB3
o9FKYay6ogJHhEvwyzhkJpQu9sIG8MfiwHZ+TZMDomUDnN1/p1EM4DyXK3dbHfUrQxrBHufz
in3SxAipY5MHMxG5serYVWzHMeHD05kKjOMWeda6UnLDOf0CUiNrdxcq3tIGrJj8eFWdWhr9
8ZroUFenAd3C7DaLWZwGZfEmBhEj2y+wIq8zvMJ26sOss+Y9oKwSvPHJGyfX3loEEzXB2m+W
HIIaTUtp3sg91G0nfvxJ5elJcFdYI1pdPD57moyhW+3j3WJFc/kYXVyQ11Gag/Gr1VfqRX85
mtkcj3ksPrm4CAbUfM8L/5SnWtuN3USo+z45AwzNuDv+XUpht9j3tkovhD4MrP9zV3TsHpPp
r5UioNknO1BDb/J06F1gdQNRPFnvataU9nH5YMv7hKRYQYEP/2pPwbBaqI41CoaN6SQ+37r/
285dwovXyBXlI1OzzrHQYdumuC8KXdKm5BRCM+Q1igpcnQiuIjHxcC4oGFgsuEBIoN+ZiSGc
70ryuMVRjsn4NIzdJyj/CHbOYffznNbtx695cdmPwp+as00FKREztbFBx94/KnrO9QCxnfdQ
uGjewfcnBLA7DrQZ7y6STn2e4FxYVWDkSkBMqUl5yX7VAPTof6CEcrtCcF/jV2L0b6IFKICW
i9cqkj98zVmcprje2qVME4ewY/TahR6yTTzEX4CvXBE9KtQuZVOBAtEoZlk3mQN0NATSqmCH
E9AAiBEIornYbxnD/QrvMb7NEhktxlwqRYTnGSUbt6JCNFYlZ/ZM4ldv6Vb7yZMHX6/Ny3zC
qYbYENhtA8/E/PY4u9vsxF4tQCV3gfOEgParCd6d9bk2tYBJ3RpwftpQ/BYw2jaHSEuRDy/7
zqi4WcZzuWplDscMhyO0KFebfGzHeuI1VJ3AR/Ve6aWRE2A+3mTPY0WXlxPtF+mWWvor8UzG
vBH5Ylkm0fKhlxedh3xheIxN5F2qtxCt6n3NIkBa7iYREZJ8AInjClN42BzB1+6ovv8/Sx0d
4vGqoV1Z9LLT8qn2vfh0JksHNYUBb83dhAKM9KRr9Kv0UUSHtAMnE+ZrZm/eVozL5bpBfgs6
lrloGRj9PkdQg+GVgzDgUhGsfz97uNzRNdq4mRZuZ3f0C7CWCR6Bp5661W1XoUa1mGQkGiKQ
xOTop8ARxUQUvwlgNVrP3hKAXNQfaNLL5TUEc0LJNCCUhDWvuFjzsKuANqycRHeiHu2thZWF
7zJorgz70UVvYrkFkAHDgVBzNLscqyt/UzqAqSrBqY1uDZFkxh0ExcoLUTxDsWjnogE0Su1C
R92D8URGX4kG+jc+nbvfPKf5ARJxBThpzsOl4uu5XDUQsipxLLklLpgYAf87HV6ybsizldVS
IixxGLyqJIo+8H0h4dznn+ujxytT1AxpAD7EB66gRLGNzEk4C1teaQtWorycB6QHXe59VPJU
ylsAKwO4hFERZs3NwpO5ng4yhK/a34Q87anZwEA4v1hKgCjfX2nAqCrDdnH/tyH4H/d6Zewv
ct7BpeUlQt4xYALfE+aw9Q/CL0dwlfCOgX/tDxjfzT+EdPNDqhAxMWWmMB5Ypc7SZFdCAzfJ
s/aBDxgv0Xfn003hPz5L1F36DtVIeoMmleWnKoy977GchtCSfB3VZ1UrBmSly17GTn/taoNK
T3jI2bBKfONRkqe7i6xtQ5xb4y/bXS3D+KVz3YWvbKfuYv8UOMqun6Pl+DxS3g3J1gj/ESWy
8jLXgSLlAzoi3y6ssbXgkQKsqcpDdzlXQvy0NEjSPTsviyWkCzW07AdRKV6IcpDKILOe8p6f
azcATFkyBW26bP5RRuNcmNxJRE/oJKV+hefPlemTrAzNzgDky6rRYMQiWpZKiVdPSQvXHXP9
gH7+kCVK0J0REgkPb49h67BzchfzzrdjUEFBQLJxoxl4ForTiAcaNOWEWDwotzA3P5Braqap
YThHHCriMMMTX5J6J3idueQhCfR4INLbU14jtpAQGzzjahNNGGfcuhZIXJRTTDrVTEMGK/AF
oc+n7x0+1kEpPJOcuA2LakSDrd7vKLpnyN6lhf4CO1zPkFVuz/on3vmWimCiUxZUKr4E3WKe
7iEwHZdn5wMaePU/rcpT8kyl4k1+URsEpUtmf86XtomzXyeUTTW9sE1jB7q7y26KURZdyfPl
HYb8+M7Pt6/+VEd8cxmZlA5sshW69/iS3KVGaEMMq5ot0kTOI84cQcQcg+6FeWXJ6PJSX9dm
ejBONALYfWnCcYPpTnDmoSGpiazb4i2gC6+976DmaOQovUr1fO2zGQ62QwsSyeN1GSjywLBz
NkI8xmrIbU1H97zexYcJp6xepoCU/b/ajHY4I7gqXBquNPn49RTWSiswC3ahAPjymE4r1Qp4
8l4TTfXNOgnOqlE8+stkocIWrZovVcqh5ge20ZRT7IuWzKTStPJgX4wUcfEiVBQi6mxF6mnW
tJ4OSZVKY/W0LF5hWSR2XzWFNLVBmOatPacmd3LSVGJkYGObwyYrtKMhAQETLZy9qy3M13Lo
xDux1hNaKiTWUFbX/fh3winZo/CLTlc7Qw24HBY5MH4DPT+YM+mo7PmhvDYgV2hj3zg7erag
7OqL7HQvxVCcLMFrd/OVHCjSPlRXOTTOHQ8rDJ89DL82mmLlZaTAQbddCqsNzJFaDEoo0Rdh
jdd7DlmI97XbbIo7CC90wGbsYXN7PcdSwvvm4v2Xoo0etPDQ++S/gXjvsvl8FriQ7+wcmHhn
solZno/beFeBOQmALUVTXenDA6wTdrNqsu5t4XddUkXVG+ApUw3rgeGsfUHv3xgJDBvlo4BL
raO5wuaZmkDGspp9NT/xwL1cYtZdviHDIQkpE1hKjSrmvcmpdv9HvTaKcZb36HU12+Rrpd1a
MFOaR+fHZIkTbdGycH8Y7MpRLQpiO+5G6mhzLIUMgpcHX/ZJeaO6dXCllfKN2izF01jcdxDr
c6ceFkzRiqvUPEdzXWUtCrLulStUsY6ZVhWd69raLL19JKQwHus6YKzG/W45uOGlK6/lEPTJ
cHoBXKwdxAi8RvuCpTgLMoRN8Ytug2GMv0vDou75WzcBZOKUIqNlscvYfQx+TT4YpeGPLzJ1
BNnWmqoFqGIXGibI2DdGMStZ+sIzs/ldPvlktnd1b9i5UxvJ+XJOesfIzSFO08wV/HEe2sjt
d1KRnrkSGXBNSb2tzDlytP5Qsp30Sjg+QKJM06fxPab4F/Hxh7JXb4jrgWym7qNQqUTCnJeK
dLBpWOSp23x6/90VSJpofdq92p9lhUTiNw0FhiVHjMaPVMYDJxBA2+fR9xKWtSqlFFEu5w7b
3XFlWo4aDDK0EUH6KFq+XlQV+FR7ELuAqm49NAdaMfwRbyknpmBX3jko3RjYNyJZT+VQsn59
pMfOFif32FL0QPXBg8wiXRB6s6bajGtEX8v1mhHC2GNYksL+Gf2ykz5AlfDovlPdY8VdjhrG
Eh7jaoNbpgNojEm9sZ6E5iKlZnft7Yje78sl5vzaV85eEjzQGUe8nau5lPjJ4tIfe1e4K8s0
NuXyjvCQDmu/gbk22cEeCAAkqwDIVFh5U+qESKw37u/7pbeytVGcpGbz3BXC1Ivr93s5OZ/l
zvsBoEjQ0rSoc4OsNRQBGzctI8oxomJJaNoUuyfHOFt21XROXrLzCBv7VyQywUzUxWcKkgOL
LCODllMfGJ5qHz7xV1M2TXPaYPP+W0m7ciS0nsW5zjZrNV+t+GL568LU6DYh3aG45qw+p3lM
c5ysZFQAXHcwPXEnuhn0T3HJgkP9f/ZurPM3MvmK42sKJ5+ROr3foSCkN1nt61CkCyCDZEz0
67aj9RLAkXhAFcjoRDMmtd9bTlsSkWQxgN5iXLMtOqU0LEEFaj34t01OYo0etSIl8VH+hQXd
TNCV33QwAL7/0m5x0kTPxK45uwdQLYRXtrUS1eAFjJpArS6R+b6/6bUfn0qTd2L8/4gexOxM
/M9g8Zc5YOgja6MshoRiPfXOe7qywXsaLhxGaTsAjIpY904ieUqEau9PD/IOROIrJ9VTVyL7
mShCtWJyDvttAGr+bldz3A8ZbdiZgKB6e4bDDLJ3jHPOa3DI+DK4cepyb31TOLEDJaV4Kcqn
djENc5dCauvmjlISGZdrWLmXlZDZXpjpY1gZmSsVk1kiwaXaONhoCLlPSMLC2/lwBTrUPyfq
VAU8X6lZQgCRyROLddEY1HEHXIOh5+ZEwi+5hF0EJqTw3HR2HzmVMbYstIEybCC4Q6276LjX
/a0806tOH5bix4eW8fL3ExV5TuQH4vp1zDdpWUuHKIICIW7/IacD9zMenTabu1FpmJZCDhzE
Y2cubeV3AAyOqom683nKCY7Waan17JFeSK4JL3z0D7oPuL9xVvIgpDxVEGhF3ZqBZLO4jyel
vgWFCQWwCE/xEeJgJvecVnXB9vis9brkVPjGQ3M8j35KBVxUENjqA6ZxaCqmX2cEKyv4Xmb7
q4IvCfMihBqI23fXcGXXatE4t2Mm6VX+de8ddQXQHYf795kH42QixxGPKjDhLFNBlxxiGs4p
fhtJ8rAGgTpyVABxx1YRY+vsFBzFjOcSTj3OUMi/VIawZpl6UMDxRY8nRgmrf4xmzedgTrRh
30YFZrbJjS2nJxoe4L+D+IjMqBJ7zwlYbhuLZjVJeMgENUnzZc24dpFwcyibQoqO74aZbdn/
b3FvxhnRiZ+Wjx/BADW1i4zrI20essDXnbcM+DAiFugwU9eS0NHQHeOfSfUHYEVV4Ik+QFwW
5MaTEC7XgucVNAY4I3IV2rRAFptCEIN1CxwxRPDSwKwlhhsoGrRO13mM6ukcsRahwuJnv3z8
x+leZ3dye5yNpQvbTLHMVGSG8RwWm/MyHHl0hMZtmrSpryFKeFHM53FeAJnhZZ4pZZbhpW1k
N7F4aVy+FeUCEwB/gs5mfHziL494jAZaDl9RI3za/xfVhTx5N2zbEwgYZ4u6qvMGzobg1aOZ
d+vw72U9U7/BRJJn7TW1PPcb26GBmvZra6pE4cJN18F2ina6JD4ArrUd6uKwtXaEUbghgrdG
l2UdUA8QVgRZmcAcHbIKsD/mcT3s2ZzYeWHNRizXdSveLrNli7NUDyqjWenag181uCgsnaKp
Oj4/qOn20rP9bv8Yn6cK2sDXg45M0D7ssgHK6lmi3NJv5xR/uzZVHaQhfHvooWueFrZEdptb
RsJ60OWNly7lIw8DbOCnoyi548cYTBBp9BCZqV++xbPvJMHTQasFpbBfKHq0/PNCI2wDh5Cf
Kh2gvNbsYcU80ZRCvuRux0dfUzE7Pt2CFTs0dJd9FiwMTDXTlYnv7FnYRcpEVOwOOk+EhTKx
+6E8qfVYhRgJjG8RIxMfGaBwzbDnM6bRU/ess/7Pmp6KHTGa3woebsKuX4w4dwa+lxAnoNEE
I/DhE5+O6Dczxbiw3f13xyLDo8RGEPtmlSg+MuttRU33U5N3ztB6VwZKkfEhYj6dutJves71
eOpq07rkDSKXQiJwgg1/FsYOhZdPyVQWKfOEAY1opNtAryS8SN1nW3ISxycdcR3YX51FmHCP
/dpkF/6J/z3ttjJGUBbHl3HjAo3HXzpJwWfZwV5ODJ5jZ+MP5l5eiosHfpLjBO53XgaQMc3O
PWlloWAXTmB0HOHcK1EKTFXTo8Na2qwr6O5oP37BGibcFCWWwPEURiYBgPm6RuhexIf9nVaB
CSNI1FcaroCZaBqwuTTm62cpYohWkgtzh2TktQ80I7CrByzGqhCvXgNKnsU2rwBDZtIWItj9
q7ZT81RD130gW5holgfmTHPFaH6drJJp5MLNWpFucd30Thg4sqWDjmenbO3H8uQHz4x+M5PN
j9CpDzXvysk02ROzKJDerIDKZrYObU4D6+PwscQspxVhA7AMXwfBpDkJ0sXCvbyn84N6O+vu
okZbWCB7yzB4SL0KXp0QfjoETlvntSBqAZor/cmJQkeLQoldyGusvl+UJG4C8rxZ0AquwfTe
4OTS3DRNoAGsRKrXH9SMgFLXetxrDHFE0vnX2TetGX/xkl4wpzUXxRQBW/pu/LkylUNB3FmJ
KR7NtqDjDzG083uC3FMr0MFm55GbRvi2y1LF4dwByZlKEDd2ruewFCMcOba8tZ34DWguswMd
Z5YY/awsNwD09O8blLEwB+4UiGtlF9+GagzgNUSQD4kFwtII8hy0kwlIRwjylMSICLWEq0+R
GYhIVo8KLIKGoaNruC7tGGSzpxMqcROHAav7Z0oR1Nn7aUPltVhWS8YDzJDvu0ReAgRXizxw
d5eM9/sfqe3jslzI9KI6AwZxztPB2qbIA6zshbzKL2drSivA9H3/g2OYVzX3BIrhihscVHTc
TB3O7TYhYme1CwqpOfGo9ggMLNs8EagG3MgsZhCpbJ2HN8c961gAvC1gmn3aMRdC2NYr69gf
0duyk1we2s8FEVJxszA6PCZXtt+Ed7899jTeNIOaF72b5YbumW7C0fFPmuzR+lIYhK6P0l8H
ay2teC8UhojPQ8OcwllTT0uY0npYqqOhtc76+J33/rrJJFj8fN58MWwnincsBd/DneU6dlbs
S/z1Vw8oAcjZY+wgjKf6wCc4I4nBBGR3WeyMjir5O0Sl2LHoe3ZuIuayjow1TiWjjVsaDQPh
SMEHOpCQ6Rgmb1HiSzqmrqWJonKvu3S/GRcSsBAABIIgAMw1S/Rg41A2gqo008h63s0O7V9m
DMImyel7x2Bc+Dbheb9Pb30j8Y6r6PGOi51J8TkKrQphLYLEEY8qjASpWTmiPl7DYuitxpYJ
xVkXZGlUcqhZpFjj2887m31Icl0iSjD9WWmeoGFlzEfP+pyXpWkaVmr2cbQOTOhOaGAGfo+H
UuryzUQ8Ir/gF5dxZNcbGv+wRB2x+XGjQCr5PW3v7ByCGpkxLAvA8h8H/WvUy+Bo4pfaVXnV
kIy7UVM88sFKHuATQhO4m3t83gQ7j+7uKo4LfzXbCjrBZsCDzAaM4phsUxbo8CJQYJbkj0M5
jIoRlArm+jMDvsVUsTDduZpS9qJL+KRewasyS8eZNznMeafHxnzIT3vt1uarvJoLp12KqaMv
oBn6jxYwhnsvZQxnCHdYfzistphq9DVDFz14g8GGHRcL+9NOX2doatxdg1ov1/hF4j+BclhO
MEtqMFnubEZjTnJjsk2AbzdQlcWrqMjDR0gMHy1xl5ZEzAY7mGp3joDyhG9WHiy/KEG9YaES
Fm85OPX7OlKOhKOhOC4kyhcGbtQ8LRBZYOHIg7OMoP6Gfo3nj74acc/o8jw90fedJ3Ok0WOY
tbYBkRW9t9GUcg47J/ECC8P4T4Qi+uA6NLaPdZdsmZO9E5M20v6nae7zy2mfmPuEWJazCSXV
crs6Mrk0NZm03dY4ilE27vK8ycZ9DzpKpjD3Oj6/uXS7NM1lZ4eQ5kpd2+LZ+rpI+qqeOWS0
IYEE2CdalfK3NaUOBjyd8MVwmE6zqfgWgV27I6+vb4oGSBY8S7TK6Zj3fZ7YVL7NmNTG3ufw
FH2ON9bYzVOOHmrGsiK+b+g3greMhxZaejpoCfMgYoXG1/ValrSTyYg3BdugF/TeU+L9G97G
fXJRD+f6nzfVi2EJMv89Hd9WI5NXmwvSkQ4UeVIhkspuas8RTkDJi8wLo6fDHx13ORDxKp4v
Zes5OOPywz4b0lwr8o6VNXrglSAeAfj6hiJlTSvIE8WQPEHV4oViuYOE+28S+yokW30Ym9Lr
Bts5xdx0nUoezIxZWJPe65/aaAd6P5roBqQ+O3v0xnPVQ7QNfxrmKAYf6q7WygZqVGzLi6Qn
jHQqh9ehuyuoCnJGnS2j129Juv/cFb6Z+NgUAoNSmFWnovtbpnhiNJ2Zc1qmvaBLYSnCsLrW
SRjtYbcMoyPkk6DWZguTlCQZMZ+Oie0cPd6/+BOL+iwhTKFH4kcYUOgCqGi3ApiXSEM72Sq8
Fa9/CdLIplUd3/L5ReRNwuf/eAKq4r4IuQ6/3clMvfRCYI8iYDAceqnoBWItPO4WdciCUYjc
oJf22MYIXsK3L9KeeSm0tlLKP3Gdjw0dcuGU18bORI+RxEhZEKN1QtM8llLekyG/bO7Ej53z
534OdVsa2IcEqTIDx0GYb0HpKMTD9xHh6LdZqr7zvXCZOGNgX3zTZq0Bg1pM/sSvQsyqdP+E
M0w0k46Lbx1IMAXcgOk/PjV1ryg7R1ZTN5rJGKWrcmMuJHZQUwoq94HV1f9kvzw+Y3zDWU6M
715DdhWDOwTHxTG+POUwCad4BSrPHma5lkSuLVi7SkjoKvb3Q8Cf7D5Q2c2wfoF4izWOhdfr
OcGGYCpFRiTeHCQ5qzst87tdiYOajduoazQvbZI+/wIxEcYs9/niOpvQzSy4D83AFg8cxHLS
zOOvzDDq+C+M65N17REkeQxSe7ogom6ypsFhtMOckadq/JnRY8imS3lcoBzhYsGWbz9cZKl6
eCmzRz+xdIq2gkZI9THXccLkFSYsQM2+/etefR086lHC3buwVh+AvyXLETx51S7C8s1/m1aI
WEgHnu0UaGJcU+8Q9vkfmUeo4FhBoakRFTkUA2FzKCiWbMLhMJM4l/PGikvgQka6faZwAV5l
7aeTOKTW/5xWjlN+EhtTSdJ9QimUsoe4p/Y7dctwlx2MPSOAHE5sLgyspYYvYMpS+WjjwYpZ
Fd+xTtPRd4inzgy1tnTFSKdPnSkPCZUJaE42Uq9+YSC32ZmfAPK22KBJMeqNDO653Id59zKp
qQ92O1qr/+mrbyNtlv7JAKNmUQ2eUeuygi+BUXZ4je6YqOleBsHu8ShvB0X2Dqmviv8+0OuA
ng3tEt7Eeyp8gLf8WO8x+BJu15MgFhsDipTYgVrFStpo0M3PrHhKt4tNskjraB+oQmn0KEoa
/eNEQp4fL8Cvnxs02eTsx75GuKEZsgN5x0L6ovpXdxU7JMmKGKvPMVgFDQAxQIQG3WF1LPTF
17Qqo9e5maWOzuvaDlTa5As5rmNGz1r8SR9yUEY495V4s9kp4pzPDD7minCY8mPyydAZRP4t
4fdw0v8dls+DdzDFnccmfeogiJdEJvL20EaQnIUHSA5EyTF+47OtLkhSLfIISVChHdiBkwXz
LgVKNtFjrXTiqMxPX12a5S/sipFdSSUUFkQ9XBFqk01zqh4l31WNG+U1kGS8pPIDR82qDVU2
UkDEizx7lJ2h0mBD64liQAriFfjB8cciK8vMIrloLUfLLziM5+2brYOB7H+MygJKd40AMCYp
4CICsObC6NRpYyYw5wXFbNtnhzvysYqcORl+asyJr2hlEJmjfvLRxkLNbT7sS8h67hjgnvUZ
F4B9UIXHF0HaZl3I9UW2j9koQxSlNtvfrqqSK0Gtixrijk6Js4Tb5kiKcHw4WCAs3LBAVmJ1
M6yQQgzgsIStLkKJRvl60qRI5ojZDBusm7KiY555WuijjCML9KrmxV/w2GLonyhrt9UiKRAh
mMbT1+ZGLVNd3S2jUYumdx1du0RV7DblUH3CKJCrN0wDsuujjZoK2BMZc1Ji4nlVsieAf7b9
SVL0HJAZbcZKDYJ/2hnxXYJovKA/SLfAG7adzj/tIRBVLvtDovRisnYjoYxbgWLv1w9K/JZp
bo9kzS/q1y8P+9/m9Namqdl4UCDahzsDgM3KzIVhdmaebfVnEsxY/EyqdKZX3c9s4jI7RSbQ
CYfpifcKU/V4yevmIt8h5t80SFrPGM28qjJAyu0xgSLYSZL0e9STaDhAuEwAaK+jbNYsnClr
PE/dv1lKZEtg69K0gupV6ICyrFmL62ARJzx0L0dJ0gMcpQpZa9HjIi5YT/J/6WFjaaqfSOaY
KMA0+Nk286wGsOd1bG4rOqcG2TZklOs40WdXkz++aelaSNf8E5nNZP6qpj0VBr1va0jnT8EL
zSmCacPP2Q70szD6dqcFty91nGzXWRZi6UWJ6DbYGqiq0qTF7li9fsKIRSAgahsDH7c9l8WZ
6DYq2nCFCxLARsyCDVhdEroELNhHZH0GCdvS4uFUxjD/Cqk0oIZSjCXL9VhCJnVGzPMHgk31
m9YBJ6bPJ7O6VsRB64H+KCTf9yjLWOj0Idrkt13Hs3BgRf6KZF9BkstsZTN9WoZ64Wfz8RRz
gh9HRi7fbpqcB6mTx0/fPTmcRqmkqaPTI8LIz8f1gYK2vJtqTLVPLgre7BGqYTL3wsi5JXsb
53R7KNS0ZJ5Ed5c4htwGUA3zK6KEmNfBx7J7/nmXYoOpz+AZ2GKw6n/GrP+jxiZTEsd9c2st
ePraLD0mpcL41zbUIgV3dx2AUyJgPgamf4oKQl0do62qN17hGyBhx6mzEMlOs8RU+FUFpDFX
orAciBo53/6DHT6uugcIct5Rul1piZlF2TVmCmri7Np19hbPOPDPZ1bUEsZUN2KfDJ6KY9KP
2htrS0F8ZJ0sbC0Rh0WgfPYqzN9dx3oRPSMdyVCKpVRDSq6GgoYCciAJtz+d1kf1PC/AG4tD
nnk5TEROkJx5n822usbpNfr+3r5NqVb4nBoWDWoJzj9z7T8/C9ZZAal0x8ZTa+tu2KTpINWu
Lea+3EG8KgZjQt+5/4nJDcc4gsAPmYaEbqDit2G/wJSbMwHSx4jcS3mqdyikea6g9EYyMggK
UE8HfhZ+vYfs8MSsdf60mMfHpaLdSJ0n1H+5mUvx3s+bMrlo96PsdXH0AtIf2yhLuuJ07d8O
4HTqITjF5Y5OmIMGrWvsmOUJlMwWWXrIh5gqHJpGzXl+tsiJNGht3GfPCtlnsKdBa6YMEvAO
mrRTorpJnUjuefkQpeAOWgAsGh0WLR1KGJhrN+OF7qpdvSIE/E9zYHRRGHpwFxXovdXgTSJO
dDRMTS3DZ0hyrhLL0intq4Y6qq3aC8Vw69KpOHJHSGFQGdD/c2Iv6sUWufDV1XY9BHcQaqFW
4Ez4Z3O6mjh16Pq/c2z3L0OzWP7+TEuNfHcJ7lFTbyL8OLIFaUZjp8wvm8kL2s6tJKj/TpJe
MfSMr/FRRJ2KntONIhaRYGgEF6FLpcQW8iOuMlPI12wpLNp6fQzP4hfDpRE9Os2eooR3hAiI
Kk6SZZw/kaLg8BCTMYYLC2mDX40sOZcNK8gujxwdc38BUhqBRIZMuKxGp3KGWGuMYqtnff/b
6ZbnijDvgAfcmCk9PApn0mAnz8ZE4shvjNRvtw5jyPCJKyjfO4OqxmIh2soOy2R3JXG7u153
dCZ07hdQ/uWO7865gobepF7K0QVvpPO6tsva1BpO5eKfv2f5JvuE6W7PBEj3vDSxkqCJymMC
3tjAXZb9Ee5yBR0NYKyK4jSH2kTFjiN11f8GaFlQtY8MoRV1nNZplw48lXjtWLebamdKQ3mU
olITq2bzAjB+gIVedoENROux9FjNlwNoOBoOMr+fkTJu7pS1SEQLnQ96v+u1DZrsHhRw34zm
d/YCLhFltcijY3Fk3i41GBbizKRJgY92A7bRpKHjLHmeadv4X/T3eDBrIO3LykqSBF1rIeWQ
Z03v1XEcp4EQkBioT0pRwx287UYsGbl3WUYhg0ULoqYQdS93AYeXl1hKKs5rt+504TYcmCLa
pgEBH1DdJDR7NxXRUNFiy1/EIyp9+CBi4zi/8vbuY7R4kru6asXPowwGc8H5eQBP+Jk2ZFGV
Ft7pYDndGPq85kSl+EBdb6IiYI5AuCyYnyFU+Y6mjEKcb+FJbb9k3NRBlqMb2XvOzwtf6vAh
rVFJ6CN2PVNiP6D4KCzS2tPkgriKvVXOgf71kjd5FwhWAUfJy/ueUITobFgRYMxqlLaHaEwC
7mGYmnilmtdLMQcRDcclgtwvPrqHn5/mlff9//p8TIdFUYWNStUUk0NMfSdTetEkAoX0ipqB
GAMbrSimyk/hSVwzC8DwF85lO07FAgkQ66g5PTFpsiabEEtr89HoEIiDb8J3+iN14lO77Sgl
59oa6L7w93KaP/JBvElVgJ7zMcuuocJzxe9qy4hgSo9OVcnNWhCBDHXsVHj/vuhUkALQbxEJ
M3ZGEMWIWiNkiPvxtk+SevlwO0bw0TyCA6RuunE+TN70o48YPJVjd7IRj+AMoulw9qDMQU5T
AwjZBb1Aa2KA5TWBKFCHkCSGu+lND7nWY9LdN+MsqfgpW4qZBhdMqYud7ZMQnxMGIq3PH4Wz
Q7UvnktVnXDLtSY8sDnuKm1vM1KuTkLiB9x6DtaFKu1GH8FwwQfRkU+y4Rf8DO8s5puXZQZ0
5NuG7hIscEtwbKLcDJW4NkHDm9RqIs/fY0fK9NtcQj8yFHk+dZuEr5C/Z3sR4L2MX3WZubYY
Jwjp7IG4RcI7q9O9ycvsR8kzbejGzHm3qISbk6gZwvZHRce8j3/UbFbvJHjQ3h2hs/WsDItp
jQ+ha0cmJs4K3UpxO5nro1Fhe8oYo+lWEzmlanM87+69RlpIKGR2Ie9Ifp4owW6+tzJGlsQo
3OPg4ua4ye6+f/dUr0Po9l+EZfYFiLXLqpzgR66ApLjZPLLhYMS7tXGd0tBstElFwU3VsMOl
qOGM7yS6aT8hN+PIGiHZe8MSFMICB/bkqULNyKaxTta0rtzuC7SlKq8rcpPJnFbaIs9BhVpD
TnuBBHIOWXYfrrnQrLGOPmPg+edSxXQULbshiLu0x0v0cGE7Qa23uD3kn5tMFVczIcduYG8p
yje8eHSa+bHXZasuUOOM7xBM+Kt/1aay99XLkim8XV5RSAAx6apjx2Wq6+h9W8xHKTw/iKRI
dbm1VxzbPF/hoH/bXSld2JCvkLYQJyRoyy7wLmnLmtIFwwSIGK254TbFWqXvek9F7mVIgwVX
KIUUk1e31TnJwuQPq/sLGexDBDQgqSFJO3aQniSkrS12O85QVHW64cCi+F2yR/VpeKAXtqFH
ccCH2mu6L+Rjvx85oygue5hMy0JDXHUWnlITjnQq+PKZrx4BvVjpqYNXlrSVykSyj75lJNvB
tJPc+2Tfh/KItk8fp0DqDDKys5vdn70fxiGEJZKK4YAMo9s4c4XWGQ9hDHX7xkojkJZPxzjH
9CvrTEkA2uhKSic3k0A7ZM+5tMJsKC8QjDLh7LD8eTOsQgEI9/rzeA+Hx/DQtH+RER1ueD6M
fWzlHKopfVJ5ObSymz5964e7BF+eUS540jJee+WUE/GuKkY+xamTi5WJ6CFqkcwdJAFHZj5K
GBEkZN17dIIz/92BAFuS5VmJrDV/YtrMGcJey0sT77H2fyKMPU3z+QFDGncxMYxRE7ITTDQl
EOz0L3i4jKv9Dbvs/yZWoJe6JdyybaJ6jU3Z/5YRWWP0tT4vnEgE8aizUc+PdW20BJ3VLO0C
G2zC/CwwZrE67vV9ETBU8Bb0zbKKtfODl0uBV5ScAcPQeC1R4pirb8yLfvbBgsDBuN+hAp3c
KCyDMcrsaXjcSPnhAyhw8BQcZvD+6PiWNU48OKWqYr7ZqNcERyc7czul1LRte46qlL7JZMU0
490aMixOtt4hLDm7buXfYheqXkVOYkIE29SknPsZLp3xV5Tw9YOkdVFkP/adOIIxVpSYNIGU
iauajvJrRYP6jLdQShR7UxKLV5bDZsK56RWaIuuxLGgzKqAqHLj50oPdOgwV9kroe4PoCIkZ
gsw4N98iQeQ8rWqZAEJV1ZFBTFBnkLRhIPTa6rsvugk/Wjm7kpxW89zEzePCBezouUUCc4Ep
vD83NT+1hYOm5a4GLPKb2zN5shboKbxIQp3jllXu9d1dHsmkDocwsbNFYwZdbCkSXqdKnto/
pMeIo0b0S2CryLyqugl2BJLQbkDPsC++2IoDZwJlo1dBivTWy4QCsh467BsNb3BXvK3UpOWW
akYdbFPmQiHc8bPMta1h8GylCFfAiazHFhvrNJVzHle/vLV7aNqWUYEch7HVrhJ+Q+g9O5gz
zv8fZ4V2ReGu30E5DAF8xyyXp5J+JoUJ+0YvPu7dsDcBMSIkkxNEaK9lgDN5/hhijkzncd79
kcnF8F4V4ocfgbFKmTjv9bvxQxc2IQPmmTLunoi9ImsTEJl3Dl6T+FFgSxE9hBfodTC8clm3
JnSei11qGIt03awKPhlhxDLfAbXqO0eLYkyio7s12V5AK8rAJoVZUBz7hjj8n+ROCpf/ecUX
06c+hpXMaUbd+E/Q+AcSHFirXkCF4KL2iyNas86pp6PeQnTm+v4+rFfYYuxM15ilqydsQ+gr
rFelotOBn6qrv371nlWTW66ddYabCNrWBUh/UOo2+tdWu2IhloNIBx68ZENM+7ibtbKtzhrh
B7X2wDf/BLG6K60upMx8GXYkABX8ql+72/SmuWKRe4sn85twCUXWGaGljhmfNuSdpRyYHleO
oUF/vI3Z0bA7UfyF36rqhN7c7hlq0LRVFYaQtxHFsEL9xkDLH5nSQCposytGW2/SfzaoRK79
lTAOAfMKkTvue6RDtWl0M3eUNlBMTyJ1Z6dccTOhYHq8ku1eAHAK0meY3cwwFzAh/18LIkrT
za1dsAaUpDULEZIMotGsMiKvlONbY16nnHI1EMfUKhWpzwlZ/GxXXyil/n9JoU1jYYFm8e6T
8o5sPvCmqXRBs13GKSwFsI3kZylFF/pVq9zN6nq8wSU07sHxEmdB4PjdCnw98Q5Gc0zRRHm5
hzm4F29V7+0VxnAuCwPlyH0Td8IOenl0JWGZT4tklmTBIlBvrIQl+VpEMP+av7cSZLZZHrKo
wbdqi5uZ2EyStGBEnevzeF2HNzasqn3QjjUCctRfdH19rLEMsC1eqHam9T4HVL3hIoJWJDEZ
T2Q6BoApVSgs79pDitCIy195jgdTvAa3QChRwRIbg3ebw5B2KhHFkX1aGnw2UnpCC3qi9nG8
6oGCSwPwY2MxpjWmlOP9KZgP2hGM5w1sBjDGklVtw/ZWIUlvpvlNx3OR78PWH+QJSKvGeCSD
asfk6oAPS69S5+/+3cIgUyIxRjjGFNfg4GQ3OaglFqKwxcXjUi+L/uAnFlWSpRKA6AN8UEz6
zDmLQoI8/DUzao5b0Qn7xIagsNNlidsuKtseuK6DKcWeUpMOTFDymTSPs+RyFCTd/Zq7Mhy0
U539+J56c6v55UG5Lw6bimd5S3ln4ky/wQ6OJuumerFicDMNI/kUyzAMf45cui0vKx6oh+fT
IkSmFeRYUijxk9UJEZMTARo3DSQJD/EEWhPS+Vx5oGkDiFgwhbcBzbzaQhvXEPz1kpxwdv6G
qgb/rkjwH1Os0tVcsf37VnyvsQpF22EVOVnGk3TCMXRsSm6+I76BD1pHXDpHHlDePpRk86hV
M2b+gHVqTu2FPdiSsmMNopeApea4+PlkAhYzx+fNO4Et/Ho+t4FmIezS0sGzOQ50tPip4lOO
CJwmUbsqYFJw5ILkyX2P4JNDU/0wi9wPv/e3cRwEiHnmmHrx6yUFMqtqV8EZ4XYizwJcmEc/
foPUJLQinUBwOA4xXV9GSTXEX6C4icRvO1h5gBOgO5ZpavC7q3XloSUnPt/g6UI53r+ukNAx
xR4OtfUP394HNmL2pMYHzScRecsf7f0FInEOsASLGrbehG5fdojFJJwfY/5n3Cg5JO94lrfg
LUX2AGIePpaPeWtnUA9+EEMEzVDr1CZ67bcfS/eVTcFLVFa/2irDYOynTHb1GQnuc3oPoc6x
AzncPdi1xPBBNPa0L63esepLLsmnMT7pJYEqfjmtzTiqEFzAHG2Km7wS4owPMkdDhqYC+VU2
s33g+tK/y/m9FMd5RyYU2+7kK+xPB1gXGiJERxfeMR3I5LTVXpFZQlksNIiSn3V+ZQM/VAJH
5JNTG6qVC/FuOuAwzC9YJfCtUjSrOad598pIVh8HwqVa4KWZPopvaryWnBJWEwPRhLB6Di0x
idKs/MJKUaUtEMcBM8aNGtBAg2AxLlNP6x96QKJQraz+WTm5ZMCj6+ALGiHB47PmtZWjp6xg
7w8oBGFGF/Vbh2KKbyrHmlcLqpn2augOv43jCru/ASC31fOmke5ONhxnTACQ5LBTI9X8cgEO
zwg7L3829pJWae2C/2Zq8lN8hPLTpEYcgQ5xKd7WmzBp8rXqFYSDmox/7cTNvywk8jzIgs9x
/bkF66apSv5O3wluZnVklEKabvXQn7ISLVjaqs9TL0CZCfslGOT1GZ4C7EpeZz3HazJi/oru
x9Yf2eDM507hcbUFsmRZqD9sY+gJ7nIbD+vdCmq0WphPv/fWCxZuPPtZ/mJyI4Kaaj+gpaZ8
ZdSl9ikpegciBnW2hTOsz6Ztiji+Qwonv3i4DVqgMverkSQhwL8pEseQF6z6CyygbZTxKFZY
I8Aaq4VoctkUcWoYk89Z9faO7FwxSQ7AyeeDh7BJZoVWudzzNhtXJp/9dinE9EYovSQo6iAB
3qfH5GCwLwoyh7K2756h3g+p7lLCZ0dgViR1jrOn1eRrwzyd1n347OARoNzLxMZlItZE04ye
kFFAFJp8QCdqiFwSgHzF4jm8TD2ea+wBYDiZ19byGth0Rmsj8OeuYPKkt7zpXLvCMOaDJBQl
l9nRvnuS/GmrbBYSZQtFkP1/SalRJHAChH/edc51iuIDTlPu+CrfekYTEX5wcp0rroEk7Fb8
nXym2jzXDBUlv0zpoQC2caVtTEjPEcHCVwjRHkps7BGoShNrh2ecPvw8pa4ZcAq1gzxQSvyU
dpoNnjeWZSNtgseAOhIVgQw4K+7CRcDris36k29bkbnWMhIAjVeX7zDBliv+91b2pM+3fmsw
mL9tPTNTHi9rDm0jFwx+8TMIt8Dh+2cDtsZ3kWiNNRbBY1LqVujLiWiLDFIB4Zm4/3JDF14+
3IL3NcoMo0zFNmPxZKYqvxxjfyi0qhjhMef3TpEoaSefog4zYmSyCze5lBovvP8HMo6JUsGq
PlZkS3Dx0pl5NiuT1rCpU2JdKPWqajnIlJeC0EwWqp2h5jIdiF16bfso73t4pk2D3ZftmFLM
NTGAKD6prgZ9YFRfBker9kImfjYQ2RBY7iGye8nAUWNiqSCo0U5joxX74yDqJ7RmpY/7DeyA
RcoVTXdKDiKiHRLov1KUpEQvz5BBtAOoaNJs7XBiCEShJQM+yPhKkbPl+da7mLx+36gdnMAQ
5T7AmyjtUT2iASunRScxwYPW56Nqo3DetHoLcshONbbCN4qiuEBbIiz6ag0hcGo6p45Lrk8P
hCtt2uRKnmPN4H1v3SRS1i3U/b5mMVODzDIV9uQ4HBWX0U5iou2zEsoDoK966YSunKEUh7qH
tY8ZLiBTznezRsbfw3jDT9P2/rHfJFT+3uRv9ERVIG2pdyjmBtJQp4lLqgg5Vi6RcMAf5WfC
JHMX3mbrlqDceN23wZZjrhdIIlWZM6q02Cvhhr+2KlHE6YoUgcwyBRRqudM6LBoy0NDR4CtI
lm6I+kc5ivJG3s+HR2xiTHIdKjQG53xKiocI/ut7dCvYIlU9h2bcGG97vTNc7rZx+ISg1fiN
TdZQDtM2IRttepqLpfT3r2MgvGDI6JdRbBZ0zjdT4aiyAX6RKHEvd+ex34lt/vXmigjVBnum
ypvVA+rc0fD7Ra4nYh2SL565SdQcSTcIlHJ4/xFsXoKy+ay0up/+AV3g0vAt1YWpiwnIdbzs
p80MAwyk5gCuxD6wZJfAsYvYP4rLAootSYhAxmYuN43kCnSxIsfArU+0BBY5yRE1/VaLC++I
aO4zSuYR0foWzaZvM4Sp/CBvBIIdsD2Cb+4kaRs+EiK5S4mN4tYJXRy7JV7yZtgPwEB4oAkQ
ynLZ7fqSK7nZyiBoPJilMSVh+oBG4KU2TVB5gzNLb7mi6zuA5LeAFBFr63GEE9oaeSON2dtQ
N5k/ZAPOeaC0gS1kcWf9mKbcemayCSN7lG9LF3L/JqRmg6mVZW+PKQ7bgSHhju4lnj4BNXF4
fsZU3gs1t6rq3EG6CBPukBdTqzZsZbT6a4lkl3vUCO9V+T93/JirEAbPK9reU3d+6b+aRfhM
zvQtWtzMHgqg9JHp080AocN03PwT1YqIeRwsLP17p59QL2cczGjZcdca21Yxv0pE3WtqD4aY
lpwUaz58UeJ02qsZot23QyNjylOQZWvlzReaJJ6hK63ws2X8zPlzlyqtLF415l1jS/9bvk2B
FvaSVasEiX+TOdfOnbde0Dcq//MD4jYx0+ms0Pebbn3s4IljgVb6VIQ03dVh3F/bTk1/jUl/
L6yKCU3ryp+IgYrzzVp6Eg4dxd35dc9GeaFl1REiKc/MbKWI4mnRguSX48g66tvGbvF6P9nv
mJfOoD1dEz5QyNwvWLxwbvZPfhLLtqLBw/q+O61Kke5OlxSfIRksEigGLeUb49WneZncvMyz
YSizZSAEs1+J+5L4sQ3ixlSndcTZc7n3/CuHmxc+E5/UqZ4VlPzc6oVrhMFltpu8jQvIM5IR
1WUhHQaEilO+Vr+gGE7gQejfW0AYNz9T3UbAcC1BlNJhzBbm8pQdamZtZraiWICDmmrQaDm1
4c8RAbXmrh3pbfuvhYbUssD3lZaeslCOI2nlbiIWRphk2jbMkaRMhqi1D+UDv9tS+1pbQgIW
J97u8QgrM3Uhtb5Jb80A2PF2gNzkBevCKug2x7GoC2W27O/pRj8/3ujODUf4zqH+27+A97Yx
/xwkU/12Wh2fUySaO0bwlOx2Oz7ykWmRL66tTV+b4DF68KhQAOo4zLcgKDYoifvBGHY/Itp+
THMYMlgropN6K64yiji72K76r0zdijqbNOhj7AHBgHdmNoWYwBq1i/+WICYPnjGyuiCtZ0M3
GtjYmV9Wlnk0k9ImEagL6YMZRKPyGt7VKiBnOBpuQK41fKCCegqX4RtafKDxv4z+CTuNjQCZ
Cw3E7okII0v0BY2NDI5B+0FYajWKgs2QR1HGmDuTpZoXuFxiO+HMImeR3QhPgdHL6LD6e7pm
inzv82MzwE+4kUHSsIkgwyde6i/L8xLIbLa985PYK+R4d/hFhQxNaUFy9uB3GyrADUYewOwD
W1aOvXTHp+DpuqI3iqfARRHMrcswRdkHAd9iDcUnNh3LLL5mQCv/Lxlj9NnnG03LXxHxRt+I
0CaA+iT7OAsOHdwfs+beIg373mNeShvLgDtmNS1t0Kxc3KRyDPn2K0Yg5UaSebi4DN7wHtTH
kU6MuOo/pTpiOVfeiLCwBlaKVfVMeFG6Tf0CNRCdB93H/4+8iD45ZI+z6c9KwdM7ewmRHAnw
Do4XUcIfwySZZrZTKQS7MAsgL70zH257JcfSkOYmHHGgc5nTRfdIXIJTV74LOTHUL4pn2gYC
HMQU/ySwqG7pgXVSDyzTI3gCldQh0TfAvFBlbUd9ZrxuGpGjW8xgFHUDibboM8afHo/Kp+xg
2kWfbzKRBcOcV7lON8lmyWIfTmhMufPzWd+0pAXCEIE2jInri0EYl2m3LGzlos5gVZy0FQBX
9WM1g75VvGbJXHxVeDCX83ylzSciq89+dLAJz9Xo0FaINo/JTrNSS3vXTwTBIavvZdIorCzG
NVG/ZwVYz0dgEQVIT0vhvl+1ICLpvCll0rzbJ0CgV+vIUuVCjIzLazfKkHd/q5R/I0L8ylHv
EuXu/cd11v9dAR/knzkD0qJ1Yy0CMD24HfTZdcuJmMgGECESmVUy0dEp/56IlBctxohKLzUN
wcHWiUrr5jjET1G8lmYLX3XWiLHHWW/40wjuMUhH9aAexvBD6DwVdnX8YLe05hIBO+RhfPyG
wcfcvjFx1H22gCvmoFQmPc1sCuDOoVBE1CVjFM0H6ivOwwnr12E1lbxjhzR4fwrbAOzx+wrf
PRzWoz7ztAyWQKRUgPYQHfB7behQ/Y0cdiav3xOlrjmOxEiZ9bqKEnbQk0Wf57x8bRGJK+Lx
4ghqYdMk7BZiK9GytXm8uxUwfNUILHX2YYRf3iqwlJMHpIPhdGapDBUEpndmfVNCJizl83sR
QLl355BKCvQf8agrDipvaR0eIYDE5vFywtr6nggsWkOCbpTqCWgBVjI89/hlQgja2U1EIL+X
vZmnpm32pyvpj0lD566qTUQS3LuGLKoVOZogbdFnZdpyRf4e26iEDcWVm6CG/IImFcB2XWq7
IloXKhW9t21zVCp/KuUfM70JaGXOuf5F5idFsJcpLZcMWApSG0XGiUcuhJTzzPjr9tBUwc5C
8Lby7eN8xmper6IbyZXKp9Ljnyh+dPCZJVd4whX8DPizBQ1JpPvWo04XLjxJGhD8dKbN5r0U
nbmFBT/TcI8Rw7rkdGFI8Lv9SLGcVy76ElsyFZkjaP0zL624DDcfHxPWP7b7YZPaUgghU3Vc
ogOwQXygsSubIyv+afF1VMvNP5q0XY9Y+jcP9CcPcucuTll0x4pLCz5jcoGmg0P8HGrzBLNl
1INT/pcs6XPUvV19sxy+4yE/CvoUWw1nHippzzDZyFqAgeqIySLGOuY60hMbT0+zT7a5bv14
MX+9S2u4lK6K6+aYmOT+QD4QiT/gB70glu+fGtdY9ROu5JhB7P3rY3XnX1dLMjtMoPtDd7FA
zf4K1ZnVD9bRNn0I8egOl3xDWdc1FubJ0WLt/7IB7nWvB0vZLhKD8jO0m1uPq7N/8Hf/mhSh
CW7Zcr5xFTSxOmo7KMviBAb4daqv+E0u3SjZhmdzM5StKOxLKZXXwYRiCO9YC34MhoizVs4D
H8UAcQrqmiBgpFYoZ0YvqY8rNf7JLRoiICMx3H18e/qhOhxvxWtxrcncLf3s9D2c1ukSJu8W
j8VrU63zkOmdUHTV9Cz0HzNsSkAlpommeLn8f40ByYpIWFPfekN30GhY/SzSWwupLAetOKLa
ux1W9dwVyG+9muHR2I0++GE9j5T8K+rZtTyA4lU3PYtMCf2EqPYEHA/gZldL/MNRbhaJhfHX
QtSHvFNQuWEShQUKk+pzZU41cMdx4w5z4EWBUFXAOHndjoUDuDJqf414DCs0YUpQFZtCJib1
3mIS5UKbJTF8h0EM8///l66pr4rcWJ1grWyAUksbUXJZLFozkYSbAa7Iafu0oqs9S7JMdwgQ
/CJTxrTOKAM3hQX9jkO52uKBgC6Cu9ppCkr84RKreV9hznQoCH0/e4XEp+1E4nLNSRXXv5Em
FmfEPkHJyVQ4eYXe0aGZRBaUyWn/8isTWybAv0N2OUu+smyjeBvj6QPMR2bvtWv5o3GzqIot
KFF+n/jlnbqhJNMS8bpT6/yPcmU/4tP02JaZ/RV3Rh4Ve5aA6QdPMLCNhRkaTX0HY6AgMYCO
UHkxlaqH8ztlfW3VphwiD6bYQcLavDaqJB6l5yaRtA6/UycnHx/mFgh0pqUrsuJhTgOHXlx+
zJ+ySdZ4y7zeyD3O3YjJ6Dp8f+dKEpBAyLgMv+2Z7Nx2MT6t0APx5WLdg2YjsS40aXnsUWdd
Jgb3ZoYsN/5PqEm5BVUgNarGAkPlthwJNMVgcPgGp6YVQ3ssmNxbmkhjcDr7pkw3Z1vyQm/Z
otF18cLJSJmp/5rswAplEokpAZVCtTRh3Y8MW/Bn+37vndLIkZu9DezTBGzXz0yTZIOwEhHO
QUcdi4VkYUcwPVgytYG2LWhww5Zbua94wG5UmrcDw+FmQKw9++vviIwwKrTnbKpGS9ayTknd
YqyxugnmfzsTsaiwOsG1c4nG/mr3h9g6TjdZ6NX3MnuhnLYlR97dDQljO65hsQm7BHeoU1je
oVrbpwY86qRgRoBCVaSjVvpLKTBwIfCg+xdiaM3elVbvCLp5ZCPJyrsbRDKR6Mp06puUi9Ba
FT9eiLqsLDjTC1U8cyCC91Em/+K1chsbfK8OEeedr7HxnqP8Djwlc93jcVYHCb5KlSI2sqD2
oCvRfa/pwGve1dyIunGDZvJMU0dRrKpLV+hUyXuIUylnBzKVoJsKPHzLx2yV+VqDbA4l1PCw
bmrgRc/hEVohWov3EViGwXrJAKZwiBLwyN4sSuL7RAA3nL7BQX60Kxrfpn4x544cnlLe1X7D
Hg5fJoWKBtbf4/+Ab35Y9atiLDiSX5vC1ekNfCqdhvxW/2SQ6OynNgLBpuXEtNod6a+zC/Ag
XY7hKiFfiyvUEafZEwY/Fju3yHR8aW2mRZ1lpsuLu/FvlBAg7KtS3dwxSCH0lpgaTQHSnkef
+L2oyopB8pAIW+F5l3KVGUKMh5WrKE3Sl+P0YOaklw+ArrIBUUc+DR1NnMUOzTJGRQ55zj6O
VV9Y71ck+oU3Rs1dNX/IQJPDuF/vtxp91BuyaLonrIMCECJg4ZGCGRxBbfFvXcl+gAFR3I1L
eWxuEJwrSe+kTnUPwqU7Zoe3AiH+gEEbg+H/dU7Zh/MbAn2XeMkwOng1fMkyJ0G0sA48t4NJ
ld0IC3rLcO+TxTFj90ta0Xq8tXhCCJhd/1gWwbJ1OhuIPEL/0enuWPXEUOiLAfYTXaX32/Gv
MaNAtjkQeR+mzlRTqzhJ+xUAzbRSYkAKXvD5cOTqkQ97hIxp9aswtkqITyxbdM71nuY4tM34
10S2JB/0ceNQS/88qF6fVbIHjUliF0jdKlPg+qQPMhVPcJBtg9Y6pL7hGEtw2p5LWm1KTrHG
f+QSbJi5qRccY3BCGW4P4BJYXzf8bO6geIinq2Jag3wmpfrQLtZPhsL4hdBMH6MbhfjqV9NW
uD8ZaIpVV3yVP89poM5Onix9rmVZPNUrTrHYasPJao/331h+zlvdL/MDlSNEqbkzkmAHWWFW
SFs8GOlLYld4/bNZyNZoL3JPrSSQTJZj9wSOJ+vf6cwsAJxfDxpFvAv5r53OdsqcNTnwJO7D
t2LCmUdngGkwe6dVKLgD2mkcLNQ5bjdrZnZqBwoJZ4fQ0jKX9WV8NWn1StZr/ghcCa5lhpzG
FAvypW2Y4fAYu6R8mBoeZxRY8zm/nhZIx0xZOhTEYXV3TX/NtRv3nfX+pDvmvSrhUEiBUxS/
4CS2NYaqGDD7+5UY1S2/SEwJgzFyX2p8PRAXG6gxXEYMagTUvTOjxUaSDWgBCJDcz/dvaHxs
35p6/s5dE88hY/Fk6FQV/Wt/rft2/XKbbnokBMwTx/MVNdH92a1+27vXKJxJdPrEFIEQkB9N
jiKgM7vH4VSvdDX4cOFbD7qVweEKgXsKpHz1LclMh1GV7aJOHqCXFFl8IJTTKUZEfE4j8z3N
QPOQtMgb2xh561MOtHTk9xv4Y0EfTRHxSyHfGO3RfD1O575UUo2vE32ZVJsDZa7V3Nr3eay3
Ht/M9t+TeJJBnDvplFsLtyxqxAF7px/cRL3iEYsHsFGOFMi1xuErInxt+9S9xswj4eMFjgiH
JYeqqXpJfVLoBg4TiaGvqaSXSNA04Vur4HAQDCPCN+ZgvR0bAuvzpYBcER8vaaPtIXGF11G5
Fu6LGe4Yt93t4Mq/aAQR9Tm/YY5RGTjq304Occ14rOBh3nU/xcfua0m9PhqMoJEWTX4tpL5j
oVRWE36jiCQG8qYzvSKRbB/MC2W8VtUT+kD5leKH/t3kHUPkeijqoc8DARlf6TqwRlSbRHCU
UALRSKqhTK+1/WInXjUmPfGf1NFN9RXFyEy3N38CCZHsIpfNWLGbSmp/Y2ZO4LAvchF2/Ixq
JHLD3uIwNRTJQL4y5N97e+uKPp6Jz+xCPZFk0O2Xqdxc1BJhv7MwHNdwNBr0tnxuaoGN4+uJ
VpTu0RY2K8hgllInTY6m0hdPzEZRTutG9Jp8F9iS6KMp6NUGgnBnBTSCMXBaxHvz2P9H+gzn
fD4IgCcBfpbxay5CPj4p50+bCIs2tgBovFdQpX5To6nG7JHRgUHJ9fmA7A4zvfyfD9DO7xGc
UYeCUN9EM2xGim4HCjJSoSEn01cDqpfbYR7bH8lmZlVwBmOQLG4U90jWY+kCg0JiwrmUxBLp
yFbRA6046TtznOLx16UJEeSoZofa8vCmNF4jziGER+XNYvjbLVk8WjX4VtC1cACXSP8DNBcy
gMnsPcQbsgD1Ok1mHDmeKnr+48tsLDVXb/J/KCTiO/hnbDf97P21HVr7ron63kr4bitNx7sX
nzFllFm+hSdpZvKrfNTYXHOAVC73PoF0Ftkzh9R9v4ZxyYLkcIGKd9BuytdOJ9KiOPpbAJtl
QSEw1RJXeeV5vs3Ke74kO29lSyB9BrCxJ/oxRTC9k+r+JTvzPZQ6e/wVAWYUn4p9JkBkEeft
PMpdCuFMNabWDnm6lbdSthSpziLOHatYAjJxfC3Y8tUuahCx6539WXSQvRYkguwaZWmHr2X+
yphoTOkQoRMD2y9ASGlV1Ym/su7arzI/HNOuTS0sQxREUViN7w9NESqCVjcH25OEGJZ/r7tZ
CRE0yr//qGmCPzl5nEJIHM7fgBOuMqoxvD07s5t4/VRIvKmIs3ImVg88HSrGFASLdX/ZRc8X
1smh1Jk7GGzzapcMIVD4Q9gTLO5c4cgRfQBCDVt2H7OvTTMadym8R4RKFZNnKJzn0pOPXJMr
KECqn/UnIecB6cR0YpYVrEbZ2DE892duZy8dDFtu5+SDdtsE1BW2HAb6xgtZ9RWKtW/6WmMb
FPz1RcCYgNYm3STWmh8NuVQB7PZqt2z6I1dtJXyqWVq8CYvjVCx/WEbE5l4H3JQEEpg/f+N9
96JvaWZce4UBaDEU+2JpWT6PALd4+S9cyCRDuzsmzn1XmrbAktv7rvc01MHTXDIyuo9YxLN9
Umx8vgP5Ff1fcucJkmM3cn5GEzCpnLSDpUrtvq8NGmfCNykBIrEVmDOMKHMzfDniqzDrFfrn
AYrEz+kG9EUIqyE4cEaDWdmshdzECfDcPfKgZ1zRKlTeOrYBooPjTFaRaECZaejUhJQJuJPE
98P4zUNVroRb1udGj0qHI24g3O+3PsafAVpKzcF5NlQeADe+9Tgvtwy9UJ/kEHNhpSQ3VhWd
JoXfGNngL3WucmfSF04bHht9V9Vg5UwqVUq070/0ES6CBY3ID/lxDBQZGDOB7vSOhFZXjHUN
89g2KhEXamBrmgX2SL++yuZi0b9ZHS/T/yYEx7OzaN1CrEPJBg6Y4BglRQHjYvfCZAELDFAV
55UXrqAn0YipNYfZZM6ARGKyER/G8/snEbvau7PeG9DHWJ+s16zV9zWnvyaXLBu2xr6XoAh/
uzDmEoEuhIAgZcFIUa8Ou1AgCGkwR1PlsPk5W6nysNKBpOX4HggABxsnNDR+fGhiEtaj1jbF
TjG6/SpFOJTlhXO+wOitl8/uwRO5ZrLz2CAr3/6grld28A25+YL+Sef03Of4pC23RDv78nCH
RJkaiwY3kAhZSPIzU+2LcxXUjpdMDkUomA/ZNtGfadOLy/q6NhKMD7OMLk0FRdVGMJskEPVZ
3AC8VshHyCH3KEb/vvJ+/3id9vwmj/ww9SmPRJ1Z1/yVdPyUG0osLMI8qIn3PMudp38ZW9Oh
R+/E/nymqTgbcy+yASRv8WzbPQyi2EYGyo4Z4Ck2zP90Qc6NErDgMm1K69khgNhmQwmrhSUT
m5L/zdRhh0voB1yZkOJnHwSXBB2TyxmkDpJGpe+Hvim14dpveN/Np5K/4Elj/8djAV7AI8Sl
osDxl0LOwf3hmmFAnaVQ/cqxjnJA9nPlwsm//xfYIhFnDZ4bipa2Fh/QYlpQDQKOlxKXdOBJ
SFG/l2uGArdTWSwqkWVCxdy91YcQPwbN1DsR9706x6ztZh8NUYmvVrS7IqT/9zK5x+dS1nym
bgsvWbhFj8plsHxNN3ecerGg2mn7VZy6ZLB14dAwDIORWFwW21a/R6Q1/qzr6+zg3SzXQlMB
u++kmTZKQ4mgD3PjXfTkYtutkn73gq3ICQPENhLe0NPqSMerDtHvU9cjRTuB9LHpNTvis5s4
2MQ3AyWI05XlULbaILPbLdNstbCnYsf2DWNMUOGW8r1w4GLkZ68yfocL/gQcEyfDbnUMlheq
70oD6FKSkhD7V4VexT7YKeekhNtoMm766CDNW6v9mtL0mr6Ee+Y+OhA4MFXnmWbWibaG87ZN
uJ7O8nqJ3lTC2DONbF4m8myWajlSxAE1r6bO2AM2I0rPKujzQ79fw/QEYA6PkunuqB37qQtY
2iaN8b98BCYneCmgkmWT7WTsrvNdcRE+bNf/MXcwutqn1nBHJA9/+ApCz3eSKEl0Ta5lhT5o
SYQipAY/O2iHB06CiKoG/T4Jg68cKrP5/vr0/EWKSGgy7isjm5HNJeYXPFS4lvzhKKZ8RTKI
mThhyWZnuGJ1tP5Jmm+beniADFxq19m60ZvbLtlfdDTX+vd8NtaTYporJ7m48Jq60kV1fnPr
XWEA8E06LPH81Kr7xTFni4u9xK0CdW7RkcEhPVJKfEJ7C7y4X18ORrXWU7Px3P7VaCKv5Yrc
kL1l1PcmIkkB/TgtsOn6b5DMwCPv0R8auqClfqA3cOnrdByPzVf7Vkg8phbz7DTKHLHRvMME
fhn+JPSIFvpfV0WU5qMU7IhRghISMc8xrf2SfCwJq6nAn/y9oZbnT9JAKosegJbHGdp1p+Yq
40z/FAl+VxqU49KgNKJYC/iCFHETCKW1i8cBdq2Tsx3Ocvs78OR4txshzqL9EMc+8+AW9TIU
bVtA6XRgmMEfwkPPMfhHIB6FTBc93CbbZltfnjYLinbkakOlXnY6vQfHal2OxTIcXgpjZswV
odIsXG6z5XAg3Z5CM8Y8+Um1gLykXKIiWvWVqx0XXBk/FWfp2cFYAcIv/wu4NvAof5gCT8ks
XgVtT/l3sVOfYxFRxdhWYv31JZR9Xnb30TmaRxlWsej/RYOyhT9nuOAW9rw1JIJ9QMfge2/0
tcgEddajXqNpps0BKsWAPX+uo4z2MvpQduHNdWRCnE8Zjx27HeFOBajIDXV8hlMhjrbP/D/W
9NoqK3LrS7JVJvPjnF5Omai4NtctWCIK8yDZGDT+KxZD36a6ANUGkmc1qwosMUJzASmiHJyY
6HA2v/5fgpP2tj+V+lzAGvH8YDUdxVsTg4ZyPAchtvzdB9uk+/BtyAR1nerl3YDVhDDGK5Tc
/KIEMvL28fZqP6hKvEuiEfP3JnaBqnqFGQWnunqjpI8gkooS11KREULZwoKhrKBKELLk4jUp
GIQM49+5JaxjbPTlNvPiaekm59i8HDzpSrjODzOF7hB2AWi+gBGb8SG3I5a2Ip+p5F8NQ/fK
r/A1yZzN7Q/OPR/m+OLxqs+waF8p+xijfhPbUMB8VxPd1oWxL5MsMTS1zUk56hkeqOTwH4Fk
5mcCP/ihLRlC4pUd2iWLj8mXxScWo2j5iWiq7VYcTqIQhFu/nAO+S1NWJaWM9McMS/dliMax
1bZ6ASetaeApUWTdGIJ+td4fP0ZQ3Gth0dCa0F5lB2NPeIBls2FPknyLNVoXEhg6XNPfq848
J9nq71pOD531JS0WuHWkAJjANbfSEnwfpzYzV1kwYjXiLA8iKSvSK2QGK6diCu8j9lC100YX
tYXQmAcqjzFijteWFVLgesHwgzFceOcXTBUwj/Oaf5qpl6i4k71bojEqqx5x4qBZR/z3fnMT
mtkeWU9hqAkhMyUhWMKY4/AQuC3RWp/wCsxT+KnGu9qtVXnNIIokaEIirpMPk5Zo/Wpk9MH/
Mwl5i4/vutFeQUlQZJFSeDj5QOWE74fu40afUGke1kCMGpm2mtDHe8XRbGh3cyic/kSHDoEF
8hvVulB8dOJjxenex6taL4bxM6R5wMZOT0QqQhuHp7JAF/cBcJUU+doKW5CyXpyXPCkY7V3H
z0u1hQ9JghGm+tioJ/1wxfsDeZu+1dsfiNCmGD2J2yNy3CqaUuaVpDCcGysL10N+SWUh1seK
Cf7uvawh3T487/tFm5s3loG4VbwsOu0ps7JcrLcGVfDp2P7RKqNC9GBpL1iky/6QiiI57oPS
7eSYk/L1wXjVD+fORwo1op52erADOloZqACw7CcEJcnAxVpDPru/w0EPjuVf0Df6mzBmWZ38
eb0PA6NGNm0EEE2X8ASND2PEX0EuoJSiOHwAAAAAAAAAAAAA

Просмотреть файл

@ -0,0 +1,124 @@
From user@domain.tld Mon Feb 20 15:20:02 2023
Message-ID: <3c1d3b003fd229f19aa3524039bd1a1c635bda7d.camel@domain.tld>
Subject: Enc+Sig
From: Christoph Wurst <user@domain.tld>
To: user@domain.tld
Date: Mon, 20 Feb 2023 15:20:02 +0100
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type="enveloped-data"
Content-Description: S/MIME Encrypted Message
Content-Transfer-Encoding: base64
User-Agent: Evolution 3.46.4
MIME-Version: 1.0
X-Evolution-Identity: 0b63ba807310263f00438acbc97aa98fa31618c8
X-Evolution-Fcc: folder://c37eb41737e0eb558bb184bb47887967188025fd/Sent
X-Evolution-Transport: c3b5e0a74d4ca20a717b63ebba6e41b6900a2969
X-Evolution-Source:
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
lURQV5QejJEAAAAAAAAAAAAA

Просмотреть файл

@ -0,0 +1,71 @@
Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type="signed-data"
Content-Description: S/MIME Signed Message
Content-Disposition: attachment; filename="smime.p7m"
Content-Transfer-Encoding: base64
MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABE5D
b250ZW50LVR5cGU6IHRleHQvcGxhaW4NCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3Rl
ZC1wcmludGFibGUNCg0KaG9pDQoAAAAAAACgggqaMIIFSTCCAzGgAwIBAgIQRFme1/jtnKba5HYu
+4yYvjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlTL01JTUUgQ0EwHhcNMjMwMjIwMTQxNTU4
WhcNMjQwMjIwMTQxNTU4WjAvMQ0wCwYDVQQDDAR1c2VyMR4wHAYJKoZIhvcNAQkBFg91c2VyQGRv
bWFpbi50bGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuG6X5wnqDc3SFi8nq86QT
7XJ0fcVjEkWOQc6NuHPCdMYKLM05n5OyCZNUo1lUSH9vKCbki9GXuOVWqqMpYN4G0D2r8hNrn6u1
wrETW8c6gy1zIkK0lBPduPTisYmsrxZX3b6mKVhlR8DCOLuyfPZnayKMJ175Z+ekFureKGAw8WPe
sENL4hs9V7O0RbfvFOXZZP8E28spvxk3jVYR6C6tj2RKAArfXIk8bLHHOhXMGJfD9GoBPG5TyBEq
zBMKHhhPjdiZRslnPxhBhMTWYII+yg9/Ko5htru3yVFdCt3h/jm2S8rYi+SOr7SjkITbnZCOaXvF
+0LsYb3zXDRWgwZR4mDHqIOyirtWpikzpvV4LQk9y5QnpUPMiPFjnd0PC3ScEY0LG2jhpfXom6w6
11Jf8Uk6jX4hGcXNsVaQy8g92J2NDbs9ZOPKNJhqy9K6QNBOA1XBVUXi8lF/OV+9nz2nyuybuavI
rjRWsRC0W0xq+lpWas2EJ2DArxkMvfEkn+Z2nAZdsDjB2so0/OEb3rKQtloCcwcb/8M99KEmdyp/
4vvBq0oOtmN0wuUSZSJnRRK1sOTAutJwYfXvqumPRiA9EcDKOso5J+eIkQKdQyiLKx/L+TYvcxJy
98iVPKdnVOIyd2E+e7Q5yccxPTwG2W4y8df2yRvwUETdfGo7jI6I4QIDAQABo3wwejAdBgNVHQ4E
FgQUxKKk9ArYZRurrDbwVcVswlDsM34wHwYDVR0jBBgwFoAU2cGiw0ExiLKw/SoiPi0AKVWjBnww
DAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwGgYDVR0RBBMwEYEPdXNlckBkb21haW4udGxk
MA0GCSqGSIb3DQEBCwUAA4ICAQApTUbcCDKsq2CwfW5laYX4ZTiFKVrLN/YXnQ6DfWD8iWonCUCe
ZlFiZDDODsTe2lNs/tvIEfkng0qIfUXckxGz84jO8BVNNzSteha09jbWEXWFuz7c688inegG149X
gDxPe8ZzmabDUKiIUmKjmYLwRGtzvsaEU4ze6dUOQaJKM1W30Q+tOj/Hp9I14TGVsJHpiV9b1RVx
5hbEDG2rBRM5VzklsG/CvYxe1tTC57Snenmd8miskuVbrXhJp7DL9JodVMvgPQeRZ9ieBFEae9AD
/JDzub/GQAbSLyvaNkMLajOXiDxOMOrd6BX9HiLTUEJ8p7NSRNYxJM4apxtTOrtRG0gnSs8Nw06a
OSGv/1zoBCtbH8mgXvIN39zcoWFRHPkCovanmvxCinyzkkar9iC7aZtsfLKSCTf6Wxf/IAOw6U2+
S9gyy8YfF3JMymdd/WSXp/C7IaIjcijiQsQN4i+d+kjVyxR/3uJoWiXLHlgwahwlkNmJ+1XLQxzn
eVVaEcknt3+xpgdYJtffDGPu0oisVZX4VWYRcCghGzJJ/a9OGdm/owZZ6cSjq7oXq1zXQxWpazv/
lN6/hiWc+XqjxxMBaYfCzu6e/WQeonxxuAWE75J3YA9Geq/fV1ZrYnKlsoba/HfrR5FZ0W1mhzUi
axCkalDefjFuKNTne6M+3hcNvjCCBUkwggMxoAMCAQICEERZntf47Zym2uR2LvuMmL4wDQYJKoZI
hvcNAQELBQAwFDESMBAGA1UEAwwJUy9NSU1FIENBMB4XDTIzMDIyMDE0MTU1OFoXDTI0MDIyMDE0
MTU1OFowLzENMAsGA1UEAwwEdXNlcjEeMBwGCSqGSIb3DQEJARYPdXNlckBkb21haW4udGxkMIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7hul+cJ6g3N0hYvJ6vOkE+1ydH3FYxJFjkHO
jbhzwnTGCizNOZ+TsgmTVKNZVEh/bygm5IvRl7jlVqqjKWDeBtA9q/ITa5+rtcKxE1vHOoMtcyJC
tJQT3bj04rGJrK8WV92+pilYZUfAwji7snz2Z2sijCde+WfnpBbq3ihgMPFj3rBDS+IbPVeztEW3
7xTl2WT/BNvLKb8ZN41WEegurY9kSgAK31yJPGyxxzoVzBiXw/RqATxuU8gRKswTCh4YT43YmUbJ
Zz8YQYTE1mCCPsoPfyqOYba7t8lRXQrd4f45tkvK2Ivkjq+0o5CE252Qjml7xftC7GG981w0VoMG
UeJgx6iDsoq7VqYpM6b1eC0JPcuUJ6VDzIjxY53dDwt0nBGNCxto4aX16JusOtdSX/FJOo1+IRnF
zbFWkMvIPdidjQ27PWTjyjSYasvSukDQTgNVwVVF4vJRfzlfvZ89p8rsm7mryK40VrEQtFtMavpa
VmrNhCdgwK8ZDL3xJJ/mdpwGXbA4wdrKNPzhG96ykLZaAnMHG//DPfShJncqf+L7watKDrZjdMLl
EmUiZ0UStbDkwLrScGH176rpj0YgPRHAyjrKOSfniJECnUMoiysfy/k2L3MScvfIlTynZ1TiMndh
Pnu0OcnHMT08BtluMvHX9skb8FBE3XxqO4yOiOECAwEAAaN8MHowHQYDVR0OBBYEFMSipPQK2GUb
q6w28FXFbMJQ7DN+MB8GA1UdIwQYMBaAFNnBosNBMYiysP0qIj4tAClVowZ8MAwGA1UdEwEB/wQC
MAAwDgYDVR0PAQH/BAQDAgWgMBoGA1UdEQQTMBGBD3VzZXJAZG9tYWluLnRsZDANBgkqhkiG9w0B
AQsFAAOCAgEAKU1G3AgyrKtgsH1uZWmF+GU4hSlayzf2F50Og31g/IlqJwlAnmZRYmQwzg7E3tpT
bP7byBH5J4NKiH1F3JMRs/OIzvAVTTc0rXoWtPY21hF1hbs+3OvPIp3oBtePV4A8T3vGc5mmw1Co
iFJio5mC8ERrc77GhFOM3unVDkGiSjNVt9EPrTo/x6fSNeExlbCR6YlfW9UVceYWxAxtqwUTOVc5
JbBvwr2MXtbUwue0p3p5nfJorJLlW614Saewy/SaHVTL4D0HkWfYngRRGnvQA/yQ87m/xkAG0i8r
2jZDC2ozl4g8TjDq3egV/R4i01BCfKezUkTWMSTOGqcbUzq7URtIJ0rPDcNOmjkhr/9c6AQrWx/J
oF7yDd/c3KFhURz5AqL2p5r8Qop8s5JGq/Ygu2mbbHyykgk3+lsX/yADsOlNvkvYMsvGHxdyTMpn
Xf1kl6fwuyGiI3Io4kLEDeIvnfpI1csUf97iaFolyx5YMGocJZDZiftVy0Mc53lVWhHJJ7d/saYH
WCbX3wxj7tKIrFWV+FVmEXAoIRsySf2vThnZv6MGWenEo6u6F6tc10MVqWs7/5Tev4YlnPl6o8cT
AWmHws7unv1kHqJ8cbgFhO+Sd2APRnqv31dWa2JypbKG2vx360eRWdFtZoc1ImsQpGpQ3n4xbijU
53ujPt4XDb4xggMzMIIDLwIBATAoMBQxEjAQBgNVBAMMCVMvTUlNRSBDQQIQRFme1/jtnKba5HYu
+4yYvjANBglghkgBZQMEAgEFAKCB3TAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3
DQEJBTEPFw0yMzAyMjAxNDIwMDJaMC8GCSqGSIb3DQEJBDEiBCCGDSEFa8Yfuw0spIJEuYhaTaWH
4LrHL5Wz2S3wiruiczA3BgkrBgEEAYI3EAQxKjAoMBQxEjAQBgNVBAMMCVMvTUlNRSBDQQIQRFme
1/jtnKba5HYu+4yYvjA5BgsqhkiG9w0BCRACCzEqoCgwFDESMBAGA1UEAwwJUy9NSU1FIENBAhBE
WZ7X+O2cptrkdi77jJi+MA0GCSqGSIb3DQEBAQUABIICAMizxRTkOPyy778bOfZIubC+w9m/j7Nk
3AisjrPHw3+j1JOlicE2IRqWn8ZFMUvsMnqtihYYoEJh7nY/krqRJznDDI8CLXqcPb0oASE/FH21
TKMlJYxHWDtXVbHJ29q6uou9Zo4jptoID7xb2LDr4JEYIMGufZtb8I4wx1OBfxddgZDz8HbgVYnc
39Mr9rp7vMahDJdAbjAhnYH2cTzu6iT2+k2xM0M/fLJM4g4Ys+YXI/FewvaMO/GM+rAlcJIr16jI
SFPqBZu0TVZrBnB6hlxbIFT0AR2TeQKthdbNhG/enE4fD+TJL5uGIHqyBBxKq1Wlu0zSyaUrARGE
H/ekfRkYZMrw7Gcq5Gei6jKxvxhqdJmoURBrZnc6XzuEaeaXnCR/QfIbJ86SO/pctjcaSdVRIvaF
ehJNr1sBvBbdnjdtdUvyjvH0T7fdvqfCoK6TH1kdtBzMyui/2I/VmuqmTpVSXHslXqMPoTrc6YjA
51LAYEoFD0MxNEp9mnSL2u+98m6fZj21+VqdJsGLtn7YE1k6uE4fIkMuIQcL0ULcymIfbSOTt59Y
6My8vYeWw3cZb+uSC2WNuY8DTGyUeZ1iecHXJDa7/RtaF7IsdDBo+r06mB+QbrsogSHQn0wpMRh3
y5Kvegi4MF7wITf/CFV1IPGbrE2E2fWX29URjk0mu7bVAAAAAAAA

Просмотреть файл

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCTCCAvGgAwIBAgIUUD02m8Fv/R2sAiuSziqI+FOb9EIwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJUy9NSU1FIENBMB4XDTIzMDIyMDE0MDIxM1oXDTMzMDIx
NzE0MDIxM1owFDESMBAGA1UEAwwJUy9NSU1FIENBMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAuSjHnnWfu73pQmX+Rl9nx2K7h2ubK5dJJYHSBmU58ndX
OZ4ntlmfWtMEWmiTf79DeLCDnBlnLGaxDxPxx75V0z3atnSuWqfokoH9RvIRtGm+
3U4vk5SS7YIl6kX6EIr+Qmw3a7GrX4MNtBOLxTcPaJNRbV9BEffrAV2Ql017kFXq
nldVVxb9uGm2MoLYokuWMShqx+WLIDESwNjKeL2qHG/M2TteGuHhm9t0ibRXOn5R
qRE8BRoImK0Rs0wJw4Iw8wrmRDgabNsH3WxbaT9Kb/UqmHa7mtE4f4Qq2RduAZN3
JCqLsTLL+jzRdRe3+AZNdjsbTZrIcwf3FqCbJYj1ALWIiiCwrVgSMK30qqp8ilDZ
Zepiq6zks521PHPT9PmGxypzRyNCRyKM6pKvN9WGlkqZ+KEyFon/a0MLEb/BDmwl
ds6AmFoqd7y2/07A4n/dj0fHjDtZRTw6k0YFQx2k4knETNKHT+z8BNkmT/lvtEwy
RLz8yeA8hahIWVdeN926o8R/8bC39qZQ8Uh7ApUjTKSTGovAqoy4ggpETtjhvsTx
DP1UppqMIaCLWEFxYBgI3FjQeEpeFf/eMepr+jjJ3X++fRaRDrDp7rlLBQEiGUOX
HelQAe9vOSvvGb49LYmKHDWYeNF0K/QadhCeAOH+ucg2INLYl8xheUT6sXW3SFUC
AwEAAaNTMFEwHQYDVR0OBBYEFNnBosNBMYiysP0qIj4tAClVowZ8MB8GA1UdIwQY
MBaAFNnBosNBMYiysP0qIj4tAClVowZ8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggIBAJODaBeTSUknv6TKH3YTp98RWXljEdgeu5ARm6zL+QFJxe/4
UnAtNAQD36Ku14g5BV3gGdnkizEfNjuldAgAYAIy25mYrirPML53o57GmqTEEI3o
0LxE7hnN8zKU0M5tFd3wH0u/z/QsG7x8/geF9wd1Up16n74RY+jcFFO1BrrBaUfJ
JNGKKmk8HY7piL/6V6nt/KTMp0etV+rrvF8BH62supADq6hQcYcqUFiMHNIYNFRq
OvMY2bRU3caN7blhrdrNYk+chX/En2gbjSPMde1V3p7sfuM7zKubVkaMxkdcrGTj
Q8T6MlGJYt3u17ah4f/pkb2ncCjoC/LJDxbbWECxlAYmN3XS6ucJwzp73DMvDK1E
Hoq7nKJEs1e1OGOOUXNFnC5d/z6lZDQcvgSgB5OujoHi/nQ9fPOGjXymxKTWgbBe
Y7cn2PmbmDW7DJNFco9gc77i9pmGx4NH0WasFoD4QWiw3ba83s1BqaZv0GAe/uZ5
NCKTh4epsiWEpfOa8u1c5UIwBhu0pRFB28U/sacS4ZVoOJYEOTCnyQ9XLCnxRgNP
N76/bGzAcGtujaUn1TEmDYd3kyt7YXUWUEC17zWjfStERUaisM0A9+hrMu7coVO1
H5Fd6iftq0NGKjMNg/ai/6b5B7ZGlfXX90C1+fER5G++G+OVd8AuEZ5pVHaC
-----END CERTIFICATE-----

Просмотреть файл

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFSTCCAzGgAwIBAgIQRFme1/jtnKba5HYu+4yYvjANBgkqhkiG9w0BAQsFADAU
MRIwEAYDVQQDDAlTL01JTUUgQ0EwHhcNMjMwMjIwMTQxNTU4WhcNMjQwMjIwMTQx
NTU4WjAvMQ0wCwYDVQQDDAR1c2VyMR4wHAYJKoZIhvcNAQkBFg91c2VyQGRvbWFp
bi50bGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuG6X5wnqDc3SF
i8nq86QT7XJ0fcVjEkWOQc6NuHPCdMYKLM05n5OyCZNUo1lUSH9vKCbki9GXuOVW
qqMpYN4G0D2r8hNrn6u1wrETW8c6gy1zIkK0lBPduPTisYmsrxZX3b6mKVhlR8DC
OLuyfPZnayKMJ175Z+ekFureKGAw8WPesENL4hs9V7O0RbfvFOXZZP8E28spvxk3
jVYR6C6tj2RKAArfXIk8bLHHOhXMGJfD9GoBPG5TyBEqzBMKHhhPjdiZRslnPxhB
hMTWYII+yg9/Ko5htru3yVFdCt3h/jm2S8rYi+SOr7SjkITbnZCOaXvF+0LsYb3z
XDRWgwZR4mDHqIOyirtWpikzpvV4LQk9y5QnpUPMiPFjnd0PC3ScEY0LG2jhpfXo
m6w611Jf8Uk6jX4hGcXNsVaQy8g92J2NDbs9ZOPKNJhqy9K6QNBOA1XBVUXi8lF/
OV+9nz2nyuybuavIrjRWsRC0W0xq+lpWas2EJ2DArxkMvfEkn+Z2nAZdsDjB2so0
/OEb3rKQtloCcwcb/8M99KEmdyp/4vvBq0oOtmN0wuUSZSJnRRK1sOTAutJwYfXv
qumPRiA9EcDKOso5J+eIkQKdQyiLKx/L+TYvcxJy98iVPKdnVOIyd2E+e7Q5yccx
PTwG2W4y8df2yRvwUETdfGo7jI6I4QIDAQABo3wwejAdBgNVHQ4EFgQUxKKk9ArY
ZRurrDbwVcVswlDsM34wHwYDVR0jBBgwFoAU2cGiw0ExiLKw/SoiPi0AKVWjBnww
DAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwGgYDVR0RBBMwEYEPdXNlckBk
b21haW4udGxkMA0GCSqGSIb3DQEBCwUAA4ICAQApTUbcCDKsq2CwfW5laYX4ZTiF
KVrLN/YXnQ6DfWD8iWonCUCeZlFiZDDODsTe2lNs/tvIEfkng0qIfUXckxGz84jO
8BVNNzSteha09jbWEXWFuz7c688inegG149XgDxPe8ZzmabDUKiIUmKjmYLwRGtz
vsaEU4ze6dUOQaJKM1W30Q+tOj/Hp9I14TGVsJHpiV9b1RVx5hbEDG2rBRM5Vzkl
sG/CvYxe1tTC57Snenmd8miskuVbrXhJp7DL9JodVMvgPQeRZ9ieBFEae9AD/JDz
ub/GQAbSLyvaNkMLajOXiDxOMOrd6BX9HiLTUEJ8p7NSRNYxJM4apxtTOrtRG0gn
Ss8Nw06aOSGv/1zoBCtbH8mgXvIN39zcoWFRHPkCovanmvxCinyzkkar9iC7aZts
fLKSCTf6Wxf/IAOw6U2+S9gyy8YfF3JMymdd/WSXp/C7IaIjcijiQsQN4i+d+kjV
yxR/3uJoWiXLHlgwahwlkNmJ+1XLQxzneVVaEcknt3+xpgdYJtffDGPu0oisVZX4
VWYRcCghGzJJ/a9OGdm/owZZ6cSjq7oXq1zXQxWpazv/lN6/hiWc+XqjxxMBaYfC
zu6e/WQeonxxuAWE75J3YA9Geq/fV1ZrYnKlsoba/HfrR5FZ0W1mhzUiaxCkalDe
fjFuKNTne6M+3hcNvg==
-----END CERTIFICATE-----

Просмотреть файл

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
8BnhmaIWSAKR7YlRKwPLWEFBOXJ30A==
-----END PRIVATE KEY-----

Просмотреть файл

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFUjCCAzqgAwIBAgIQPNReuUyM+DUIY0jIs9wXfjANBgkqhkiG9w0BAQsFADAU
MRIwEAYDVQQDDAlTL01JTUUgQ0EwHhcNMjMwMTI2MTAxMjIzWhcNMjQwMTI2MTAx
MjIzWjAzMQ0wCwYDVQQDDAR1c2VyMSIwIAYJKoZIhvcNAQkBFhN1c2VyQGltYXAu
bG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwVjeJ4l+
pCov/MV4a8O6mMSpYx9pbbVnjmd5pYsArCXUL2n7Qhr3Qj1RVwZ6MddUcU8kM5FE
Vp5ycSVkaM7PNo8OW5OC+/zSgVDrUv5VAo8bqiYZ/MKCouNszPdX8u4rMMu/Y4aE
fPhTr1R0IhN9h7Y+OLm+c/JUo+m8fVq0RyE2NvlPhTbgo7tNoYE4e3oYlBsygwJD
EklZy2xoYxIB7mD4l4qz2Vl3itPaNoXIrwYDjd2qzvtWFovWQB0Bmf0hIlySC35F
tr0NhQ4DgKIOY5ccCx8j2KYXm/o9stsqApWCaaeiW+Q/rjkgw4hqANFHW2ypzPRh
5aXEU5iDMCuEpgn0W2Bp/JE+7qhsnHT5vkfperr05lvNsmHEzlRJDfYpk2+9yDD7
Q28fLK65lyuru+KMzXHsS78qTFd7JvLp+vWaOxb2zG1ogLhqlRdLObfWIrIoqJPp
3uBZRBdEUT3MrZYC4MCHzPxSFD+xd9Eu/xpyhcSVrfjhn4Aot7/kpyzUrenSD14i
g0Jq6fXSE2g4wp3rTM7fhxCEDYjDSiSgt7O2QAFC4zymTX9CPf54QCK9qEFdiTdp
MyWz88Lg07m6Ey+op57OUL8KyJYwrc7ORFROOP3QXUU+TFjqmDUsbEdESPQ0Z2FO
OuKiwldAchZAXau5QsZOLfAJSLpMsUayOX8CAwEAAaOBgDB+MB0GA1UdDgQWBBS/
P8Av04tJ+6Z0kqwioumY50NcyDAfBgNVHSMEGDAWgBST8/DtdlOT1x9VFvUptIoH
HYduFTAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIFoDAeBgNVHREEFzAVgRN1
c2VyQGltYXAubG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4ICAQDBvfQbG9XF0ion
5gV58tw9hSqqlgmi3wmCJRjgH4F/Dh8zabEGKMhnUNGwt8szwkmKgfo70hF0hiDF
ntT/lrIpZaaj+xaHLty4uexXlGVyDGeRiOn9hrwjfrrV/MpqP76U9iswWaH9uUdz
HuoNeqwaBiUj8C5OBsbLyo8MKHNvwPf/xrt9kjdYOmpKICeD2cC4RCjJ6J6QDV1k
pimigtziVHqepNRQZBxEtSgbtsfpfvKOIPw37fwK66BNL/Y3iXWnLpx+VP3LWR2N
igqCPXpXFG7vPH2y4Vj6TtdIjUpiG+WRW6TVxGSEjj81X5tyxlKBezuNhUty30Wr
8Ugpzt+8wjL7W1u+ZU0t3ZXZUo9ezmGeSLVFInJ9X3zdVOgB0wDffu5yWyUJh4FT
GVw1YGBo2wZS9wD6Hd4aLuy/Xkk5LdBohM57f3ncPgf+zz8g+29M0xEe7+SllWLH
0Enh+Ymyfs01bYIeYkjQCmVGWbKsOBqqvLXItFEFOG9H4UKkJOvjkqEpANQa7Epp
I99G7c6rHFjAC+oDuznsxB7RSGKywQpGG+TnDbn5H98BOzr2HOQwBL8jZR/XG1xk
o300Ya8us3kJquq9/FMAzReZfQL10ZxycHyhDJNJhs4X60M9AqG4guhUA8ZEI1Ik
aYAHcRXlsEqTNzseEXHIYWHosJLHRQ==
-----END CERTIFICATE-----

Просмотреть файл

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
z3DzrkpG8RTO/QaTJc8FV3bAm40B
-----END PRIVATE KEY-----