Page MenuHome GnuPG

No OneTemporary

diff --git a/src/mail.cpp b/src/mail.cpp
index 29caeca..7621494 100644
--- a/src/mail.cpp
+++ b/src/mail.cpp
@@ -1,2298 +1,2355 @@
/* @file mail.h
* @brief High level class to work with Outlook Mailitems.
*
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "dialogs.h"
#include "common.h"
#include "mail.h"
#include "eventsinks.h"
#include "attachment.h"
#include "mapihelp.h"
+#include "mimemaker.h"
#include "message.h"
#include "revert.h"
#include "gpgoladdin.h"
#include "mymapitags.h"
#include "parsecontroller.h"
#include "gpgolstr.h"
#include "windowmessages.h"
#include "mlang-charset.h"
#include <gpgme++/configuration.h>
#include <gpgme++/tofuinfo.h>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
#include <gpgme++/context.h>
#include <gpgme++/keylistresult.h>
#include <gpg-error.h>
#include <map>
#include <set>
#include <vector>
#include <memory>
#undef _
#define _(a) utf8_gettext (a)
using namespace GpgME;
static std::map<LPDISPATCH, Mail*> g_mail_map;
static std::map<std::string, Mail*> g_uid_map;
static std::set<std::string> uids_searched;
static bool
in_de_vs_mode()
{
/* We cache the values only once. A change requires restart.
This is because checking this is very expensive as gpgconf
spawns each process to query the settings. */
static bool checked;
static bool vs_mode;
if (checked)
{
return vs_mode;
}
Error err;
const auto components = Configuration::Component::load (err);
log_debug ("%s:%s: Checking for de-vs mode.",
SRCNAME, __func__);
if (err)
{
log_error ("%s:%s: Failed to get gpgconf components: %s",
SRCNAME, __func__, err.asString ());
checked = true;
vs_mode = false;
return vs_mode;
}
for (const auto &component: components)
{
if (component.name () && !strcmp (component.name (), "gpg"))
{
for (const auto &option: component.options ())
{
if (option.name () && !strcmp (option.name (), "compliance") &&
option.currentValue ().stringValue () &&
!stricmp (option.currentValue ().stringValue (), "de-vs"))
{
log_debug ("%s:%s: Detected de-vs mode",
SRCNAME, __func__);
checked = true;
vs_mode = true;
return vs_mode;
}
}
checked = true;
vs_mode = false;
return vs_mode;
}
}
checked = true;
vs_mode = false;
return false;
}
#define COPYBUFSIZE (8 * 1024)
Mail::Mail (LPDISPATCH mailitem) :
m_mailitem(mailitem),
m_processed(false),
m_needs_wipe(false),
m_needs_save(false),
m_crypt_successful(false),
m_is_smime(false),
m_is_smime_checked(false),
m_is_signed(false),
m_is_valid(false),
m_close_triggered(false),
m_is_html_alternative(false),
m_needs_encrypt(false),
m_moss_position(0),
m_crypto_flags(0),
m_type(MSGTYPE_UNKNOWN)
{
if (get_mail_for_item (mailitem))
{
log_error ("Mail object for item: %p already exists. Bug.",
mailitem);
return;
}
m_event_sink = install_MailItemEvents_sink (mailitem);
if (!m_event_sink)
{
/* Should not happen but in that case we don't add us to the list
and just release the Mail item. */
log_error ("%s:%s: Failed to install MailItemEvents sink.",
SRCNAME, __func__);
gpgol_release(mailitem);
return;
}
g_mail_map.insert (std::pair<LPDISPATCH, Mail *> (mailitem, this));
}
GPGRT_LOCK_DEFINE(dtor_lock);
Mail::~Mail()
{
/* This should fix a race condition where the mail is
deleted before the parser is accessed in the decrypt
thread. The shared_ptr of the parser then ensures
that the parser is alive even if the mail is deleted
while parsing. */
gpgrt_lock_lock (&dtor_lock);
std::map<LPDISPATCH, Mail *>::iterator it;
detach_MailItemEvents_sink (m_event_sink);
gpgol_release(m_event_sink);
it = g_mail_map.find(m_mailitem);
if (it != g_mail_map.end())
{
g_mail_map.erase (it);
}
if (!m_uuid.empty())
{
auto it2 = g_uid_map.find(m_uuid);
if (it2 != g_uid_map.end())
{
g_uid_map.erase (it2);
}
}
gpgol_release(m_mailitem);
if (!m_uuid.empty())
{
log_oom_extra ("%s:%s: destroyed: %p uuid: %s",
SRCNAME, __func__, this, m_uuid.c_str());
}
else
{
log_oom_extra ("%s:%s: non crypto mail: %p destroyed",
SRCNAME, __func__, this);
}
gpgrt_lock_unlock (&dtor_lock);
}
Mail *
Mail::get_mail_for_item (LPDISPATCH mailitem)
{
if (!mailitem)
{
return NULL;
}
std::map<LPDISPATCH, Mail *>::iterator it;
it = g_mail_map.find(mailitem);
if (it == g_mail_map.end())
{
return NULL;
}
return it->second;
}
Mail *
Mail::get_mail_for_uuid (const char *uuid)
{
if (!uuid)
{
return NULL;
}
auto it = g_uid_map.find(std::string(uuid));
if (it == g_uid_map.end())
{
return NULL;
}
return it->second;
}
bool
Mail::is_valid_ptr (const Mail *mail)
{
auto it = g_mail_map.begin();
while (it != g_mail_map.end())
{
if (it->second == mail)
return true;
++it;
}
return false;
}
int
Mail::pre_process_message ()
{
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
return 0;
}
log_oom_extra ("%s:%s: GetBaseMessage OK.",
SRCNAME, __func__);
/* Change the message class here. It is important that
we change the message class in the before read event
regardless if it is already set to one of GpgOL's message
classes. Changing the message class (even if we set it
to the same value again that it already has) causes
Outlook to reconsider what it "knows" about a message
and reread data from the underlying base message. */
mapi_change_message_class (message, 1);
/* TODO: Unify this so mapi_change_message_class returns
a useful value already. */
m_type = mapi_get_message_type (message);
/* Create moss attachments here so that they are properly
hidden when the item is read into the model. */
m_moss_position = mapi_mark_or_create_moss_attach (message, m_type);
if (!m_moss_position)
{
log_error ("%s:%s: Failed to find moss attachment.",
SRCNAME, __func__);
m_type = MSGTYPE_UNKNOWN;
}
gpgol_release (message);
return 0;
}
static LPDISPATCH
get_attachment (LPDISPATCH mailitem, int pos)
{
LPDISPATCH attachment;
LPDISPATCH attachments = get_oom_object (mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
return NULL;
}
std::string item_str;
int count = get_oom_int (attachments, "Count");
if (count < 1)
{
log_debug ("%s:%s: Invalid attachment count: %i.",
SRCNAME, __func__, count);
gpgol_release (attachments);
return NULL;
}
if (pos > 0)
{
item_str = std::string("Item(") + std::to_string(pos) + ")";
}
else
{
item_str = std::string("Item(") + std::to_string(count) + ")";
}
attachment = get_oom_object (attachments, item_str.c_str());
gpgol_release (attachments);
return attachment;
}
/** Helper to check that all attachments are hidden, to be
called before crypto. */
int
Mail::check_attachments () const
{
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
return 1;
}
int count = get_oom_int (attachments, "Count");
if (!count)
{
gpgol_release (attachments);
return 0;
}
std::string message;
if (is_encrypted () && is_signed ())
{
message += _("Not all attachments were encrypted or signed.\n"
"The unsigned / unencrypted attachments are:\n\n");
}
else if (is_signed ())
{
message += _("Not all attachments were signed.\n"
"The unsigned attachments are:\n\n");
}
else if (is_encrypted ())
{
message += _("Not all attachments were encrypted.\n"
"The unencrypted attachments are:\n\n");
}
else
{
gpgol_release (attachments);
return 0;
}
bool foundOne = false;
for (int i = 1; i <= count; i++)
{
std::string item_str;
item_str = std::string("Item(") + std::to_string (i) + ")";
LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ());
if (!oom_attach)
{
log_error ("%s:%s: Failed to get attachment.",
SRCNAME, __func__);
continue;
}
VARIANT var;
VariantInit (&var);
if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var) ||
(var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE))
{
foundOne = true;
message += get_oom_string (oom_attach, "DisplayName");
message += "\n";
}
VariantClear (&var);
gpgol_release (oom_attach);
}
gpgol_release (attachments);
if (foundOne)
{
message += "\n";
message += _("Note: The attachments may be encrypted or signed "
"on a file level but the GpgOL status does not apply to them.");
wchar_t *wmsg = utf8_to_wchar (message.c_str ());
wchar_t *wtitle = utf8_to_wchar (_("GpgOL Warning"));
MessageBoxW (get_active_hwnd (), wmsg, wtitle,
MB_ICONWARNING|MB_OK);
xfree (wmsg);
xfree (wtitle);
}
return 0;
}
/** Get the cipherstream of the mailitem. */
static LPSTREAM
get_attachment_stream (LPDISPATCH mailitem, int pos)
{
if (!pos)
{
log_debug ("%s:%s: Called with zero pos.",
SRCNAME, __func__);
return NULL;
}
LPDISPATCH attachment = get_attachment (mailitem, pos);
LPSTREAM stream = NULL;
if (!attachment)
{
// For opened messages that have ms-tnef type we
// create the moss attachment but don't find it
// in the OOM. Try to find it through MAPI.
HRESULT hr;
log_debug ("%s:%s: Failed to find MOSS Attachment. "
"Fallback to MAPI.", SRCNAME, __func__);
LPMESSAGE message = get_oom_message (mailitem);
if (!message)
{
log_debug ("%s:%s: Failed to get MAPI Interface.",
SRCNAME, __func__);
return NULL;
}
hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (hr)
{
log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
return stream;
}
LPATTACH mapi_attachment = NULL;
mapi_attachment = (LPATTACH) get_oom_iunknown (attachment,
"MapiObject");
gpgol_release (attachment);
if (!mapi_attachment)
{
log_debug ("%s:%s: Failed to get MapiObject of attachment: %p",
SRCNAME, __func__, attachment);
return NULL;
}
if (FAILED (mapi_attachment->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream,
0, MAPI_MODIFY, (LPUNKNOWN*) &stream)))
{
log_debug ("%s:%s: Failed to open stream for mapi_attachment: %p",
SRCNAME, __func__, mapi_attachment);
gpgol_release (mapi_attachment);
}
return stream;
}
#if 0
This should work. But Outlook says no. See the comment in set_pa_variant
about this. I left the code here as an example how to work with
safearrays and how this probably should work.
static int
copy_data_property(LPDISPATCH target, std::shared_ptr<Attachment> attach)
{
VARIANT var;
VariantInit (&var);
/* Get the size */
off_t size = attach->get_data ().seek (0, SEEK_END);
attach->get_data ().seek (0, SEEK_SET);
if (!size)
{
TRACEPOINT;
return 1;
}
if (!get_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
{
log_debug("Have variant. type: %x", var.vt);
}
else
{
log_debug("failed to get variant.");
}
/* Set the type to an array of unsigned chars (OLE SAFEARRAY) */
var.vt = VT_ARRAY | VT_UI1;
/* Set up the bounds structure */
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].cElements = static_cast<unsigned long> (size);
rgsabound[0].lLbound = 0;
/* Create an OLE SAFEARRAY */
var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound);
if (var.parray == NULL)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
void *buffer = NULL;
/* Get a safe pointer to the array */
if (SafeArrayAccessData(var.parray, &buffer) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
/* Copy data to it */
size_t nread = attach->get_data ().read (buffer, static_cast<size_t> (size));
if (nread != static_cast<size_t> (size))
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
/*/ Unlock the variant data */
if (SafeArrayUnaccessData(var.parray) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
VariantClear(&var);
return 0;
}
#endif
static int
copy_attachment_to_file (std::shared_ptr<Attachment> att, HANDLE hFile)
{
char copybuf[COPYBUFSIZE];
size_t nread;
/* Security considerations: Writing the data to a temporary
file is necessary as neither MAPI manipulation works in the
read event to transmit the data nor Property Accessor
works (see above). From a security standpoint there is a
short time where the temporary files are on disk. Tempdir
should be protected so that only the user can read it. Thus
we have a local attack that could also take the data out
of Outlook. FILE_SHARE_READ is necessary so that outlook
can read the file.
A bigger concern is that the file is manipulated
by another software to fake the signature state. So
we keep the write exlusive to us.
We delete the file before closing the write file handle.
*/
/* Make sure we start at the beginning */
att->get_data ().seek (0, SEEK_SET);
while ((nread = att->get_data ().read (copybuf, COPYBUFSIZE)))
{
DWORD nwritten;
if (!WriteFile (hFile, copybuf, nread, &nwritten, NULL))
{
log_error ("%s:%s: Failed to write in tmp attachment.",
SRCNAME, __func__);
return 1;
}
if (nread != nwritten)
{
log_error ("%s:%s: Write truncated.",
SRCNAME, __func__);
return 1;
}
}
return 0;
}
/** Sets some meta data on the last attachment added. The meta
data is taken from the attachment object. */
static int
fixup_last_attachment (LPDISPATCH mail, std::shared_ptr<Attachment> attachment)
{
/* Currently we only set content id */
if (attachment->get_content_id ().empty())
{
log_debug ("%s:%s: Content id not found.",
SRCNAME, __func__);
return 0;
}
LPDISPATCH attach = get_attachment (mail, -1);
if (!attach)
{
log_error ("%s:%s: No attachment.",
SRCNAME, __func__);
return 1;
}
int ret = put_pa_string (attach,
PR_ATTACH_CONTENT_ID_DASL,
attachment->get_content_id ().c_str());
gpgol_release (attach);
return ret;
}
/** Helper to update the attachments of a mail object in oom.
does not modify the underlying mapi structure. */
static int
add_attachments(LPDISPATCH mail,
std::vector<std::shared_ptr<Attachment> > attachments)
{
int err = 0;
for (auto att: attachments)
{
if (att->get_display_name().empty())
{
log_error ("%s:%s: Ignoring attachment without display name.",
SRCNAME, __func__);
continue;
}
wchar_t* wchar_name = utf8_to_wchar (att->get_display_name().c_str());
HANDLE hFile;
wchar_t* wchar_file = get_tmp_outfile (GpgOLStr (att->get_display_name().c_str()),
&hFile);
if (copy_attachment_to_file (att, hFile))
{
log_error ("%s:%s: Failed to copy attachment %s to temp file",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
if (add_oom_attachment (mail, wchar_file, wchar_name))
{
log_error ("%s:%s: Failed to add attachment: %s",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
if (!DeleteFileW (wchar_file))
{
log_error ("%s:%s: Failed to delete tmp attachment for: %s",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
xfree (wchar_file);
xfree (wchar_name);
err = fixup_last_attachment (mail, att);
}
return err;
}
GPGRT_LOCK_DEFINE(parser_lock);
static DWORD WINAPI
do_parsing (LPVOID arg)
{
gpgrt_lock_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: canceling parsing for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
/* This takes a shared ptr of parser. So the parser is
still valid when the mail is deleted. */
auto parser = mail->parser();
gpgrt_lock_unlock (&dtor_lock);
gpgrt_lock_lock (&parser_lock);
/* We lock the parser here to avoid too many
decryption attempts if there are
multiple mailobjects which might have already
been deleted (e.g. by quick switches of the mailview.)
Let's rather be a bit slower.
*/
log_debug ("%s:%s: preparing the parser for: %p",
SRCNAME, __func__, arg);
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: cancel for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&parser_lock);
return 0;
}
if (!parser)
{
log_error ("%s:%s: no parser found for mail: %p",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&parser_lock);
return -1;
}
parser->parse();
do_in_ui_thread (PARSING_DONE, arg);
gpgrt_lock_unlock (&parser_lock);
return 0;
}
bool
Mail::is_crypto_mail() const
{
if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
m_type == MSGTYPE_SMIME)
{
/* Not a message for us. */
return false;
}
return true;
}
int
Mail::decrypt_verify()
{
if (!is_crypto_mail())
{
return 0;
}
if (m_needs_wipe)
{
log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
SRCNAME, __func__, m_mailitem);
return 1;
}
set_uuid ();
m_processed = true;
/* Insert placeholder */
char *placeholder_buf;
if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
is_smime() ? "S/MIME" : "OpenPGP",
_("Encrypted message"),
_("Please wait while the message is being decrypted / verified...")) == -1)
{
log_error ("%s:%s: Failed to format placeholder.",
SRCNAME, __func__);
return 1;
}
if (opt.prefer_html)
{
m_orig_body = get_oom_string (m_mailitem, "HTMLBody");
if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
m_orig_body = get_oom_string (m_mailitem, "Body");
if (put_oom_string (m_mailitem, "Body", placeholder_buf))
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
}
xfree (placeholder_buf);
/* Do the actual parsing */
auto cipherstream = get_attachment_stream (m_mailitem, m_moss_position);
if (!cipherstream)
{
log_debug ("%s:%s: Failed to get cipherstream.",
SRCNAME, __func__);
return 1;
}
m_parser = std::shared_ptr <ParseController> (new ParseController (cipherstream, m_type));
m_parser->setSender(GpgME::UserID::addrSpecFromString(get_sender().c_str()));
log_mime_parser ("%s:%s: Parser for \"%s\" is %p",
SRCNAME, __func__, get_subject ().c_str(), m_parser.get());
gpgol_release (cipherstream);
HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0,
NULL);
if (!parser_thread)
{
log_error ("%s:%s: Failed to create decrypt / verify thread.",
SRCNAME, __func__);
}
CloseHandle (parser_thread);
return 0;
}
void find_and_replace(std::string& source, const std::string &find,
const std::string &replace)
{
for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length();
}
}
void
Mail::update_body()
{
const auto error = m_parser->get_formatted_error ();
if (!error.empty())
{
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
if (put_oom_string (m_mailitem, "Body",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
return;
}
if (m_verify_result.error())
{
log_error ("%s:%s: Verification failed. Restoring Body.",
SRCNAME, __func__);
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
if (put_oom_string (m_mailitem, "Body", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
return;
}
// No need to carry body anymore
m_orig_body = std::string();
auto html = m_parser->get_html_body ();
/** Outlook does not show newlines if \r\r\n is a newline. We replace
these as apparently some other buggy MUA sends this. */
find_and_replace (html, "\r\r\n", "\r\n");
if (opt.prefer_html && !html.empty())
{
char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(),
html.c_str(), html.size());
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : "");
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
return;
}
auto body = m_parser->get_body ();
find_and_replace (body, "\r\r\n", "\r\n");
char *converted = ansi_charset_to_utf8 (m_parser->get_body_charset().c_str(),
body.c_str(), body.size());
int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
return;
}
void
Mail::parsing_done()
{
TRACEPOINT;
log_oom_extra ("Mail %p Parsing done for parser: %p",
this, m_parser.get());
if (!m_parser)
{
/* This should not happen but it happens when outlook
sends multiple ItemLoad events for the same Mail
Object. In that case it could happen that one
parser was already done while a second is now
returning for the wrong mail (as it's looked up
by uuid.)
We have a check in get_uuid that the uuid was
not in the map before (and the parser is replaced).
So this really really should not happen. We
handle it anyway as we crash otherwise.
It should not happen because the parser is only
created in decrypt_verify which is called in the
read event. And even in there we check if the parser
was set.
*/
log_error ("%s:%s: No parser obj. For mail: %p",
SRCNAME, __func__, this);
return;
}
/* Store the results. */
m_decrypt_result = m_parser->decrypt_result ();
m_verify_result = m_parser->verify_result ();
m_crypto_flags = 0;
if (!m_decrypt_result.isNull())
{
m_crypto_flags |= 1;
}
if (m_verify_result.numSignatures())
{
m_crypto_flags |= 2;
}
update_sigstate ();
m_needs_wipe = true;
TRACEPOINT;
/* Set categories according to the result. */
update_categories();
TRACEPOINT;
/* Update the body */
update_body();
TRACEPOINT;
/* Check that there are no unsigned / unencrypted messages. */
check_attachments ();
/* Update attachments */
if (add_attachments (m_mailitem, m_parser->get_attachments()))
{
log_error ("%s:%s: Failed to update attachments.",
SRCNAME, __func__);
}
/* Invalidate UI to set the correct sig status. */
m_parser = nullptr;
gpgoladdin_invalidate_ui ();
TRACEPOINT;
return;
}
int
Mail::encrypt_sign ()
{
int err = -1,
flags = 0;
protocol_t proto = opt.enable_smime ? PROTOCOL_UNKNOWN : PROTOCOL_OPENPGP;
if (!needs_crypto())
{
return 0;
}
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
return err;
}
flags = get_gpgol_draft_info_flags (message);
const auto window = get_active_hwnd ();
- m_do_inline = opt.inline_pgp;
+ if (m_is_gsuite)
+ {
+ auto att_table = mapi_create_attach_table (message, 0);
+ int n_att_usable = count_usable_attachments (att_table);
+ mapi_release_attach_table (att_table);
+ /* Check for attachments if we have some abort. */
+ if (n_att_usable)
+ {
+ MessageBox (window,
+ _("G Suite Sync breaks outgoing crypto mails "
+ "with attachments.\nUsing crypto and attachments "
+ "with G Suite Sync is not supported.\n\n"
+ "See: https://dev.gnupg.org/T3545 for details."),
+ _("GpgOL: Oops, G Suite Sync account detected"),
+ MB_ICONINFORMATION|MB_OK);
+ return -1;
+ }
+ if (flags == 2)
+ {
+ MessageBox (window,
+ _("G Suite Sync breaks outgoing signed mails.\n"
+ "Ensuring mail integrity (signing) with G Suite Sync "
+ "is not supported.\n\n"
+ "See: https://dev.gnupg.org/T3545 for details."),
+ _("GpgOL: Oops, G Suite Sync account detected"),
+ MB_ICONINFORMATION|MB_OK);
+ return -1;
+ }
+ if (flags == 3)
+ {
+ if(MessageBox (window,
+ _("G Suite Sync breaks outgoing signed mails.\n"
+ "Ensuring mail integrity (signing) with G Suite Sync "
+ "is not supported.\n\n"
+ "See: https://dev.gnupg.org/T3545 for details.\n\n"
+ "Do you want to only encrypt the message?"),
+ _("GpgOL: Oops, G Suite Sync account detected"),
+ MB_ICONINFORMATION|MB_YESNO) != IDYES)
+ {
+ return -1;
+ }
+ else
+ {
+ flags = 1;
+ }
+ }
+ }
+
+ m_do_inline = m_is_gsuite ? true : opt.inline_pgp;
EnableWindow (window, FALSE);
if (flags == 3)
{
log_debug ("%s:%s: Sign / Encrypting message",
SRCNAME, __func__);
err = message_sign_encrypt (message, proto,
NULL, get_sender ().c_str (), this);
}
else if (flags == 2)
{
err = message_sign (message, proto,
NULL, get_sender ().c_str (), this);
}
else if (flags == 1)
{
err = message_encrypt (message, proto,
NULL, get_sender ().c_str (), this);
}
else
{
log_debug ("%s:%s: Unknown flags for crypto: %i",
SRCNAME, __func__, flags);
}
log_debug ("%s:%s: Status: %i",
SRCNAME, __func__, err);
EnableWindow (window, TRUE);
gpgol_release (message);
m_crypt_successful = !err;
return err;
}
int
Mail::needs_crypto ()
{
LPMESSAGE message = get_oom_message (m_mailitem);
bool ret;
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
return false;
}
ret = get_gpgol_draft_info_flags (message);
gpgol_release(message);
return ret;
}
int
Mail::wipe (bool force)
{
if (!m_needs_wipe && !force)
{
return 0;
}
log_debug ("%s:%s: Removing plaintext from mailitem: %p.",
SRCNAME, __func__, m_mailitem);
if (put_oom_string (m_mailitem, "HTMLBody",
""))
{
if (put_oom_string (m_mailitem, "Body",
""))
{
log_debug ("%s:%s: Failed to wipe mailitem: %p.",
SRCNAME, __func__, m_mailitem);
return -1;
}
return -1;
}
m_needs_wipe = false;
return 0;
}
int
Mail::update_oom_data ()
{
LPDISPATCH sender = NULL;
log_debug ("%s:%s", SRCNAME, __func__);
/* Update the body format. */
m_is_html_alternative = get_oom_int (m_mailitem, "BodyFormat") > 1;
/* Store the body. It was not obvious for me (aheinecke) how
to access this through MAPI. */
if (m_is_html_alternative)
{
log_debug ("%s:%s: Is html alternative mail.", SRCNAME, __func__);
const char * html_body = get_oom_string (m_mailitem, "HTMLBody");
if (html_body)
{
m_html_body = html_body;
}
}
/* For some reason outlook may store the recipient address
in the send using account field. If we have SMTP we prefer
the SenderEmailAddress string. */
if (is_crypto_mail ())
{
/* This is the case where we are reading a mail and not composing.
When composing we need to use the SendUsingAccount because if
you send from the folder of userA but change the from to userB
outlook will keep the SenderEmailAddress of UserA. This is all
so horrible. */
char *type = get_oom_string (m_mailitem, "SenderEmailType");
if (type && !strcmp ("SMTP", type))
{
char *senderMail = get_oom_string (m_mailitem, "SenderEmailAddress");
if (senderMail)
{
log_debug ("%s:%s Sender found", SRCNAME, __func__);
m_sender = senderMail;
xfree (senderMail);
xfree (type);
return 0;
}
}
xfree (type);
}
sender = get_oom_object (m_mailitem, "SendUsingAccount");
if (sender)
{
char *buf = get_oom_string (sender, "SmtpAddress");
+ char *dispName = get_oom_string (sender, "DisplayName");
gpgol_release (sender);
+
+ /* Check for G Suite account */
+ if (dispName && !strcmp ("G Suite", dispName))
+ {
+ m_is_gsuite = true;
+ }
+ xfree (dispName);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 1", SRCNAME, __func__);
m_sender = buf;
xfree (buf);
return 0;
}
xfree (buf);
}
/* Fallback to Sender object */
sender = get_oom_object (m_mailitem, "Sender");
if (sender)
{
char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
gpgol_release (sender);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__);
m_sender = buf;
xfree (buf);
return 0;
}
xfree (buf);
}
/* We don't have s sender object or SendUsingAccount,
well, in that case fall back to the current user. */
sender = get_oom_object (m_mailitem, "Session.CurrentUser");
if (sender)
{
char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
gpgol_release (sender);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__);
m_sender = buf;
xfree (buf);
return 0;
}
xfree (buf);
}
log_debug ("%s:%s: All fallbacks failed.",
SRCNAME, __func__);
return -1;
}
std::string
Mail::get_sender ()
{
if (m_sender.empty())
update_oom_data();
return m_sender;
}
int
Mail::close_all_mails ()
{
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
TRACEPOINT;
std::map<LPDISPATCH, Mail *> mail_map_copy = g_mail_map;
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
/* XXX For non racy code the is_valid_ptr check should not
be necessary but we crashed sometimes closing a destroyed
mail. */
if (!is_valid_ptr (it->second))
{
log_debug ("%s:%s: Already deleted mail for %p",
SRCNAME, __func__, it->first);
continue;
}
if (!it->second->is_crypto_mail())
{
continue;
}
if (close_inspector (it->second) || close (it->second))
{
log_error ("Failed to close mail: %p ", it->first);
/* Should not happen */
if (is_valid_ptr (it->second) && it->second->revert())
{
err++;
}
}
}
return err;
}
int
Mail::revert_all_mails ()
{
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
for (it = g_mail_map.begin(); it != g_mail_map.end(); ++it)
{
if (it->second->revert ())
{
log_error ("Failed to revert mail: %p ", it->first);
err++;
continue;
}
it->second->set_needs_save (true);
if (!invoke_oom_method (it->first, "Save", NULL))
{
log_error ("Failed to save reverted mail: %p ", it->second);
err++;
continue;
}
}
return err;
}
int
Mail::wipe_all_mails ()
{
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
for (it = g_mail_map.begin(); it != g_mail_map.end(); ++it)
{
if (it->second->wipe ())
{
log_error ("Failed to wipe mail: %p ", it->first);
err++;
}
}
return err;
}
int
Mail::revert ()
{
int err = 0;
if (!m_processed)
{
return 0;
}
err = gpgol_mailitem_revert (m_mailitem);
if (err == -1)
{
log_error ("%s:%s: Message revert failed falling back to wipe.",
SRCNAME, __func__);
return wipe ();
}
/* We need to reprocess the mail next time around. */
m_processed = false;
m_needs_wipe = false;
return 0;
}
bool
Mail::is_smime ()
{
msgtype_t msgtype;
LPMESSAGE message;
if (m_is_smime_checked)
{
return m_is_smime;
}
message = get_oom_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: No message?",
SRCNAME, __func__);
return false;
}
msgtype = mapi_get_message_type (message);
m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED ||
msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED;
/* Check if it is an smime mail. Multipart signed can
also be true. */
if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED)
{
char *proto;
char *ct = mapi_get_message_content_type (message, &proto, NULL);
if (ct && proto)
{
m_is_smime = (!strcmp (proto, "application/pkcs7-signature") ||
!strcmp (proto, "application/x-pkcs7-signature"));
}
else
{
log_error ("Protocol in multipart signed mail.");
}
xfree (proto);
xfree (ct);
}
gpgol_release (message);
m_is_smime_checked = true;
return m_is_smime;
}
static std::string
get_string (LPDISPATCH item, const char *str)
{
char *buf = get_oom_string (item, str);
if (!buf)
return std::string();
std::string ret = buf;
xfree (buf);
return ret;
}
std::string
Mail::get_subject() const
{
return get_string (m_mailitem, "Subject");
}
std::string
Mail::get_body() const
{
return get_string (m_mailitem, "Body");
}
std::string
Mail::get_html_body() const
{
return get_string (m_mailitem, "HTMLBody");
}
char **
Mail::get_recipients() const
{
LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients");
if (!recipients)
{
TRACEPOINT;
return nullptr;
}
auto ret = get_oom_recipients (recipients);
gpgol_release (recipients);
return ret;
}
int
Mail::close_inspector (Mail *mail)
{
LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
HRESULT hr;
DISPID dispid;
if (!inspector)
{
log_debug ("%s:%s: No inspector.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (inspector, "Close");
if (dispid != DISPID_UNKNOWN)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Failed to close inspector: %#lx",
SRCNAME, __func__, hr);
return -1;
}
}
return 0;
}
/* static */
int
Mail::close (Mail *mail)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
log_oom_extra ("%s:%s: Invoking close for: %p",
SRCNAME, __func__, mail->item());
mail->set_close_triggered (true);
int rc = invoke_oom_method_with_parms (mail->item(), "Close",
NULL, &dispparams);
log_oom_extra ("%s:%s: Reurned from close",
SRCNAME, __func__);
return rc;
}
void
Mail::set_close_triggered (bool value)
{
m_close_triggered = value;
}
bool
Mail::get_close_triggered () const
{
return m_close_triggered;
}
static const UserID
get_uid_for_sender (const Key &k, const char *sender)
{
UserID ret;
if (!sender)
{
return ret;
}
if (!k.numUserIDs())
{
log_debug ("%s:%s: Key without uids",
SRCNAME, __func__);
return ret;
}
for (const auto uid: k.userIDs())
{
if (!uid.email())
{
log_error ("%s:%s: skipping uid without email.",
SRCNAME, __func__);
continue;
}
auto normalized_uid = uid.addrSpec();
auto normalized_sender = UserID::addrSpecFromString(sender);
if (normalized_sender.empty() || normalized_uid.empty())
{
log_error ("%s:%s: normalizing '%s' or '%s' failed.",
SRCNAME, __func__, uid.email(), sender);
continue;
}
if (normalized_sender == normalized_uid)
{
ret = uid;
}
}
return ret;
}
void
Mail::update_sigstate ()
{
std::string sender = get_sender();
if (sender.empty())
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return;
}
if (m_verify_result.isNull())
{
log_debug ("%s:%s: No verify result.",
SRCNAME, __func__);
return;
}
if (m_verify_result.error())
{
log_debug ("%s:%s: verify error.",
SRCNAME, __func__);
return;
}
for (const auto sig: m_verify_result.signatures())
{
m_is_signed = true;
m_uid = get_uid_for_sender (sig.key(), sender.c_str());
if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal &&
sig.validity() != Signature::Validity::Full &&
sig.validity() != Signature::Validity::Ultimate))
{
/* For our category we only care about trusted sigs. And
the UID needs to match.*/
continue;
}
if (sig.validity() == Signature::Validity::Marginal)
{
const auto tofu = m_uid.tofuInfo();
if (!tofu.isNull() &&
(tofu.validity() != TofuInfo::Validity::BasicHistory &&
tofu.validity() != TofuInfo::Validity::LargeHistory))
{
/* Marginal is only good enough without tofu.
Otherwise we wait for basic trust. */
log_debug ("%s:%s: Discarding marginal signature."
"With too little history.",
SRCNAME, __func__);
continue;
}
}
log_debug ("%s:%s: Classified sender as verified uid validity: %i",
SRCNAME, __func__, m_uid.validity());
m_sig = sig;
m_is_valid = true;
return;
}
log_debug ("%s:%s: No signature with enough trust. Using first",
SRCNAME, __func__);
m_sig = m_verify_result.signature(0);
return;
}
bool
Mail::is_valid_sig ()
{
return m_is_valid;
}
void
Mail::remove_categories ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
remove_category (m_mailitem, decCategory);
remove_category (m_mailitem, verifyCategory);
}
/* Now for some tasty hack: Outlook sometimes does
not show the new categories properly but instead
does some weird scrollbar thing. This can be
avoided by resizing the message a bit. But somehow
this only needs to be done once.
Weird isn't it? But as this workaround worked let's
do it programatically. Fun. Wan't some tomato sauce
with this hack? */
static void
resize_active_window ()
{
HWND wnd = get_active_hwnd ();
static std::vector<HWND> resized_windows;
if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) {
/* We only need to do this once per window. XXX But sometimes we also
need to do this once per view of the explorer. So for now this might
break but we reduce the flicker. A better solution would be to find
the current view and track that. */
return;
}
if (!wnd)
{
TRACEPOINT;
return;
}
RECT oldpos;
if (!GetWindowRect (wnd, &oldpos))
{
TRACEPOINT;
return;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
/* Anything smaller then 19 was ignored when the window was
* maximized on Windows 10 at least with a 1980*1024
* resolution. So I assume it's at least 1 percent.
* This is all hackish and ugly but should work for 90%...
* hopefully.
*/
(int)oldpos.right - oldpos.left - 20,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
return;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
(int)oldpos.right - oldpos.left,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
return;
}
resized_windows.push_back(wnd);
}
void
Mail::update_categories ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
if (is_valid_sig())
{
add_category (m_mailitem, verifyCategory);
}
else
{
remove_category (m_mailitem, verifyCategory);
}
if (!m_decrypt_result.isNull())
{
add_category (m_mailitem, decCategory);
}
else
{
/* As a small safeguard against fakes we remove our
categories */
remove_category (m_mailitem, decCategory);
}
resize_active_window();
return;
}
bool
Mail::is_signed() const
{
return m_verify_result.numSignatures() > 0;
}
bool
Mail::is_encrypted() const
{
return !m_decrypt_result.isNull();
}
int
Mail::set_uuid()
{
char *uuid;
if (!m_uuid.empty())
{
/* This codepath is reached by decrypt again after a
close with discard changes. The close discarded
the uuid on the OOM object so we have to set
it again. */
log_debug ("%s:%s: Resetting uuid for %p to %s",
SRCNAME, __func__, this,
m_uuid.c_str());
uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str());
}
else
{
uuid = get_unique_id (m_mailitem, 1, nullptr);
log_debug ("%s:%s: uuid for %p set to %s",
SRCNAME, __func__, this, uuid);
}
if (!uuid)
{
log_debug ("%s:%s: Failed to get/set uuid for %p",
SRCNAME, __func__, m_mailitem);
return -1;
}
if (m_uuid.empty())
{
m_uuid = uuid;
Mail *other = get_mail_for_uuid (uuid);
if (other)
{
/* According to documentation this should not
happen as this means that multiple ItemLoad
events occured for the same mailobject without
unload / destruction of the mail.
But it happens. If you invalidate the UI
in the selection change event Outlook loads a
new mailobject for the mail. Might happen in
other surprising cases. We replace in that
case as experiments have shown that the last
mailobject is the one that is visible.
Still troubling state so we log this as an error.
*/
log_error ("%s:%s: There is another mail for %p "
"with uuid: %s replacing it.",
SRCNAME, __func__, m_mailitem, uuid);
delete other;
}
g_uid_map.insert (std::pair<std::string, Mail *> (m_uuid, this));
log_debug ("%s:%s: uuid for %p is now %s",
SRCNAME, __func__, this,
m_uuid.c_str());
}
xfree (uuid);
return 0;
}
/* Returns 2 if the userid is ultimately trusted.
Returns 1 if the userid is fully trusted but has
a signature by a key for which we have a secret
and which is ultimately trusted. (Direct trust)
0 otherwise */
static int
level_4_check (const UserID &uid)
{
if (uid.isNull())
{
return 0;
}
if (uid.validity () == UserID::Validity::Ultimate)
{
return 2;
}
if (uid.validity () == UserID::Validity::Full)
{
for (const auto sig: uid.signatures ())
{
const char *sigID = sig.signerKeyID ();
if (sig.isNull() || !sigID)
{
/* should not happen */
TRACEPOINT;
continue;
}
/* Direct trust information is not available
through gnupg so we cached the keys with ultimate
trust during parsing and now see if we find a direct
trust path.*/
for (const auto secKey: ParseController::get_ultimate_keys ())
{
/* Check that the Key id of the key matches */
const char *secKeyID = secKey.keyID ();
if (!secKeyID || strcmp (secKeyID, sigID))
{
continue;
}
/* Check that the userID of the signature is the ultimately
trusted one. */
const char *sig_uid_str = sig.signerUserID();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
for (const auto signer_uid: secKey.userIDs ())
{
if (signer_uid.validity() != UserID::Validity::Ultimate)
{
TRACEPOINT;
continue;
}
const char *signer_uid_str = signer_uid.id ();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
if (!strcmp(sig_uid_str, signer_uid_str))
{
/* We have a match */
log_debug ("%s:%s: classified %s as ultimate because "
"it was signed by uid %s of key %s",
SRCNAME, __func__, signer_uid_str, sig_uid_str,
secKeyID);
return 1;
}
}
}
}
}
return 0;
}
std::string
Mail::get_crypto_summary ()
{
const int level = get_signature_level ();
bool enc = is_encrypted ();
if (level == 4 && enc)
{
return _("Security Level 4");
}
if (level == 4)
{
return _("Trust Level 4");
}
if (level == 3 && enc)
{
return _("Security Level 3");
}
if (level == 3)
{
return _("Trust Level 3");
}
if (level == 2 && enc)
{
return _("Security Level 2");
}
if (level == 2)
{
return _("Trust Level 2");
}
if (enc)
{
return _("Encrypted");
}
if (is_signed ())
{
/* Even if it is signed, if it is not validly
signed it's still completly insecure as anyone
could have signed this. So we avoid the label
"signed" here as this word already implies some
security. */
return _("Insecure");
}
return _("Insecure");
}
std::string
Mail::get_crypto_one_line()
{
bool sig = is_signed ();
bool enc = is_encrypted ();
if (sig || enc)
{
if (sig && enc)
{
return _("Signed and encrypted message");
}
else if (sig)
{
return _("Signed message");
}
else if (enc)
{
return _("Encrypted message");
}
}
return _("Insecure message");
}
std::string
Mail::get_crypto_details()
{
std::string message;
/* No signature with keys but error */
if (!is_encrypted() && !is_signed () && m_verify_result.error())
{
message = _("You cannot be sure who sent, "
"modified and read the message in transit.");
message += "\n\n";
message += _("The message was signed but the verification failed with:");
message += "\n";
message += m_verify_result.error().asString();
return message;
}
/* No crypo, what are we doing here? */
if (!is_encrypted () && !is_signed ())
{
return _("You cannot be sure who sent, "
"modified and read the message in transit.");
}
/* Handle encrypt only */
if (is_encrypted() && !is_signed ())
{
if (in_de_vs_mode ())
{
if (m_sig.isDeVs())
{
message += _("The encryption was VS-NfD-compliant.");
}
else
{
message += _("The encryption was not VS-NfD-compliant.");
}
}
message += "\n\n";
message += _("You cannot be sure who sent the message because "
"it is not signed.");
return message;
}
bool keyFound = true;
bool isOpenPGP = m_sig.key().isNull() ? !is_smime() :
m_sig.key().protocol() == Protocol::OpenPGP;
char *buf;
bool hasConflict = false;
int level = get_signature_level ();
log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i",
SRCNAME, __func__, m_sig.validity(), m_sig.summary(),
level);
if (level == 4)
{
/* level 4 check for direct trust */
int four_check = level_4_check (m_uid);
if (four_check == 2 && m_sig.key().hasSecret ())
{
message = _("You signed this message.");
}
else if (four_check == 1)
{
message = _("The senders identity was certified by yourself.");
}
else if (four_check == 2)
{
message = _("The sender is allowed to certify identities for you.");
}
else
{
log_error ("%s:%s:%i BUG: Invalid sigstate.",
SRCNAME, __func__, __LINE__);
return message;
}
}
else if (level == 3 && isOpenPGP)
{
/* Level three is only reachable through web of trust and no
direct signature. */
message = _("The senders identity was certified by several trusted people.");
}
else if (level == 3 && !isOpenPGP)
{
/* Level three is the only level for trusted S/MIME keys. */
gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"),
m_sig.key().issuerName());
message = buf;
xfree (buf);
}
else if (level == 2 && m_uid.tofuInfo ().isNull ())
{
/* Marginal trust through pgp only */
message = _("Some trusted people "
"have certified the senders identity.");
}
else if (level == 2)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
/* i18n note signcount is always pulral because with signcount 1 we
* would not be in this branch. */
gpgrt_asprintf (&buf, _("The senders address is trusted, because "
"you have established a communication history "
"with this address starting on %s.\n"
"You encrypted %i and verified %i messages since."),
time, m_uid.tofuInfo().encrCount(),
m_uid.tofuInfo().signCount ());
xfree (time);
message = buf;
xfree (buf);
}
else if (level == 1)
{
/* This could be marginal trust through pgp, or tofu with little
history. */
if (m_uid.tofuInfo ().signCount() == 1)
{
message += _("The senders signature was verified for the first time.");
}
else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because "
"you only verified %i messages and encrypted %i messages to "
"it since %s."),
m_uid.tofuInfo().signCount (),
m_uid.tofuInfo().encrCount (), time);
xfree (time);
message = buf;
xfree (buf);
}
}
else
{
/* Now we are in level 0, this could be a technical problem, no key
or just unkown. */
message = is_encrypted () ? _("But the sender address is not trustworthy because:") :
_("The sender address is not trustworthy because:");
message += "\n";
keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing);
bool general_problem = true;
/* First the general stuff. */
if (m_sig.summary() & Signature::Summary::Red)
{
message += _("The signature is invalid: \n");
}
else if (m_sig.summary() & Signature::Summary::SysError ||
m_verify_result.numSignatures() < 1)
{
message += _("There was an error verifying the signature.\n");
}
else if (m_sig.summary() & Signature::Summary::SigExpired)
{
message += _("The signature is expired.\n");
}
else
{
message += isOpenPGP ? _("The used key") : _("The used certificate");
message += " ";
general_problem = false;
}
/* Now the key problems */
if ((m_sig.summary() & Signature::Summary::KeyMissing))
{
message += _("is not available.");
}
else if ((m_sig.summary() & Signature::Summary::KeyRevoked))
{
message += _("is revoked.");
}
else if ((m_sig.summary() & Signature::Summary::KeyExpired))
{
message += _("is expired.");
}
else if ((m_sig.summary() & Signature::Summary::BadPolicy))
{
message += _("is not meant for signing.");
}
else if ((m_sig.summary() & Signature::Summary::CrlMissing))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::CrlTooOld))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::TofuConflict) ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
message += _("is not the same as the key that was used "
"for this address in the past.");
hasConflict = true;
}
else if (m_uid.isNull())
{
gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."),
get_sender().c_str());
message += buf;
xfree (buf);
}
else if (((m_sig.validity() & Signature::Validity::Undefined) ||
(m_sig.validity() & Signature::Validity::Unknown) ||
(m_sig.summary() == Signature::Summary::None) ||
(m_sig.validity() == 0))&& !general_problem)
{
/* Bit of a catch all for weird results. */
if (isOpenPGP)
{
message += _("is not certified by any trustworthy key.");
}
else
{
message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.");
}
}
else if (m_uid.isRevoked())
{
message += _("The sender marked this address as revoked.");
}
else if ((m_sig.validity() & Signature::Validity::Never))
{
message += _("is marked as not trustworthy.");
}
}
message += "\n\n";
if (in_de_vs_mode ())
{
if (is_signed ())
{
if (m_sig.isDeVs ())
{
message += _("The signature is VS-NfD-compliant.");
}
else
{
message += _("The signature is not VS-NfD-compliant.");
}
message += "\n";
}
if (is_encrypted ())
{
if (m_decrypt_result.isDeVs ())
{
message += _("The encryption is VS-NfD-compliant.");
}
else
{
message += _("The encryption is not VS-NfD-compliant.");
}
message += "\n\n";
}
else
{
message += "\n";
}
}
if (hasConflict)
{
message += _("Click here to change the key used for this address.");
}
else if (keyFound)
{
message += isOpenPGP ? _("Click here for details about the key.") :
_("Click here for details about the certificate.");
}
else
{
message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") :
_("Click here to search the certificate on the configured X509 keyserver.");
}
return message;
}
int
Mail::get_signature_level () const
{
if (!m_is_signed)
{
return 0;
}
if (m_uid.isNull ())
{
/* No m_uid matches our sender. */
return 0;
}
if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate ||
(m_uid.validity () == UserID::Validity::Full &&
level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs()))
{
return 4;
}
if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
(!in_de_vs_mode () || m_sig.isDeVs()))
{
return 3;
}
if (m_is_valid)
{
return 2;
}
if (m_sig.validity() == Signature::Validity::Marginal)
{
return 1;
}
if (m_sig.summary() & Signature::Summary::TofuConflict ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
return 0;
}
return 0;
}
int
Mail::get_crypto_icon_id () const
{
int level = get_signature_level ();
int offset = is_encrypted () ? ENCRYPT_ICON_OFFSET : 0;
return IDI_LEVEL_0 + level + offset;
}
const char*
Mail::get_sig_fpr() const
{
if (!m_is_signed || m_sig.isNull())
{
return nullptr;
}
return m_sig.fingerprint();
}
static DWORD WINAPI
do_locate (LPVOID arg)
{
char *recipient = (char*) arg;
log_debug ("%s:%s searching key for recipient: \"%s\"",
SRCNAME, __func__, recipient);
Context *ctx = Context::createForProtocol (OpenPGP);
if (!ctx)
{
TRACEPOINT;
return 0;
}
ctx->setKeyListMode (GpgME::Extern | GpgME::Local);
ctx->startKeyListing (recipient, false);
std::vector<Key> keys;
Error err;
do {
keys.push_back (ctx->nextKey(err));
} while (!err);
keys.pop_back ();
ctx->endKeyListing ();
delete ctx;
if (keys.size ())
{
log_debug ("%s:%s found key for recipient: \"%s\"",
SRCNAME, __func__, recipient);
}
xfree (recipient);
// do_in_ui_thread (UNKNOWN, NULL);
return 0;
}
GPGRT_LOCK_DEFINE(uids_searched_lock);
/** Try to locate the keys for all recipients */
void Mail::locate_keys()
{
gpgrt_lock_lock (&uids_searched_lock);
char ** recipients = get_recipients ();
if (!recipients)
{
TRACEPOINT;
return;
}
for (int i = 0; recipients[i]; i++)
{
std::string recp = recipients[i];
if (uids_searched.find (recp) == uids_searched.end ())
{
uids_searched.insert (recp);
HANDLE thread = CreateThread (NULL, 0, do_locate,
(LPVOID) strdup(recipients[i]), 0,
NULL);
CloseHandle (thread);
}
xfree (recipients[i]);
}
xfree (recipients);
gpgrt_lock_unlock (&uids_searched_lock);
}
bool
Mail::is_html_alternative () const
{
return m_is_html_alternative;
}
const std::string &
Mail::get_cached_html_body () const
{
return m_html_body;
}
int
Mail::get_crypto_flags () const
{
return m_crypto_flags;
}
void
Mail::set_needs_encrypt (bool value)
{
m_needs_encrypt = value;
}
bool
Mail::needs_encrypt() const
{
return m_needs_encrypt;
}
diff --git a/src/mail.h b/src/mail.h
index 2ea7834..d26f477 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -1,363 +1,364 @@
/* @file mail.h
* @brief High level class to work with Outlook Mailitems.
*
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MAIL_H
#define MAIL_H
#include "oomhelp.h"
#include "mapihelp.h"
#include "gpgme++/verificationresult.h"
#include "gpgme++/decryptionresult.h"
#include "gpgme++/key.h"
#include <string>
#include <future>
class ParseController;
/** @brief Data wrapper around a mailitem.
*
* This class is intended to bundle all that we know about
* a Mail. Due to the restrictions in Outlook we sometimes may
* need additional information that is not available at the time
* like the sender address of an exchange account in the afterWrite
* event.
*
* This class bundles such information and also provides a way to
* access the event handler of a mail.
*/
class Mail
{
public:
/** @brief Construct a mail object for the item.
*
* This also installs the event sink for this item.
*
* The mail object takes ownership of the mailitem
* reference. Do not Release it! */
Mail (LPDISPATCH mailitem);
~Mail ();
/** @brief looks for existing Mail objects for the OOM mailitem.
@returns A reference to an existing mailitem or NULL in case none
could be found.
*/
static Mail* get_mail_for_item (LPDISPATCH mailitem);
/** @brief looks for existing Mail objects in the uuid map.
Only objects for which set_uid has been called can be found
in the uid map. Get the Unique ID of a mailitem thorugh get_unique_id
@returns A reference to an existing mailitem or NULL in case none
could be found.
*/
static Mail* get_mail_for_uuid (const char *uuid);
/** @brief looks for existing Mail objects.
@returns A reference to an existing mailitem or NULL in case none
could be found. Can be used to check if a mail object was destroyed.
*/
static bool is_valid_ptr (const Mail *mail);
/** @brief wipe the plaintext from all known Mail objects.
*
* This is intended as a "cleanup" call to be done on unload
* to avoid leaking plaintext in case we are deactivated while
* some mails still have their plaintext inserted.
*
* @returns the number of errors that occured.
*/
static int wipe_all_mails ();
/** @brief revert all known Mail objects.
*
* Similar to wipe but works on MAPI to revert our attachment
* dance and restore an original MIME mail.
*
* @returns the number of errors that occured.
*/
static int revert_all_mails ();
/** @brief close all known Mail objects.
*
* Close our mail with discard changes set to true.
* This discards the plaintext / attachments. Afterwards
* it calls save if neccessary to sync back the collected
* property changes.
*
* This is the nicest of our three "Clean plaintext"
* functions. Will fallback to revert if closing fails.
* Closed mails are deleted.
*
* @returns the number of errors that occured.
*/
static int close_all_mails ();
/** @brief Reference to the mailitem. Do not Release! */
LPDISPATCH item () { return m_mailitem; }
/** @brief Pre process the message. Ususally to be called from BeforeRead.
*
* This function assumes that the base message interface can be accessed
* and calles the MAPI Message handling which changes the message class
* to enable our own handling.
*
* @returns 0 on success.
*/
int pre_process_message ();
/** @brief Decrypt / Verify the mail.
*
* Sets the needs_wipe and was_encrypted variable.
*
* @returns 0 on success. */
int decrypt_verify ();
/** @brief do crypto operations as selected by the user.
*
* Initiates the crypto operations according to the gpgol
* draft info flags.
*
* @returns 0 on success. */
int encrypt_sign ();
/** @brief Necessary crypto operations were completed successfully. */
bool crypto_successful () { return !needs_crypto() || m_crypt_successful; }
/** @brief Message should be encrypted and or signed.
0: No
1: Encrypt
2: Sign
3: Encrypt & Sign
*/
int needs_crypto ();
/** @brief wipe the plaintext from the message and encrypt attachments.
*
* @returns 0 on success; */
int wipe (bool force = false);
/** @brief revert the message to the original mail before our changes.
*
* @returns 0 on success; */
int revert ();
/** @brief update some data collected from the oom
*
* This updates cached values from the OOM that are not available
* in MAPI events like after Write.
*
* For Exchange 2013 at least we don't have any other way to get the
* senders SMTP address then through the object model. So we have to
* store the sender address for later events that do not allow us to
* access the OOM but enable us to work with the underlying MAPI structure.
*
* It also updated the is_html_alternative value.
*
* @returns 0 on success */
int update_oom_data ();
/** @brief get sender SMTP address (UTF-8 encoded).
*
* If the sender address has not been set through update_sender this
* calls update_sender before returning the sender.
*
* @returns A reference to the utf8 sender address. Or NULL. */
std::string get_sender ();
/** @brief get the subject string (UTF-8 encoded).
*
* @returns the subject or an empty string. */
std::string get_subject () const;
/** @brief Is this a crypto mail handled by gpgol.
*
* Calling this is only valid after a message has been processed.
*
* @returns true if the mail was either signed or encrypted and we processed
* it.
*/
bool is_crypto_mail () const;
/** @brief This mail needs to be actually written.
*
* @returns true if the next write event should not be canceled.
*/
bool needs_save () { return m_needs_save; }
/** @brief set the needs save state.
*/
void set_needs_save (bool val) { m_needs_save = val; }
/** @brief is this mail an S/MIME mail.
*
* @returns true for smime messages.
*/
bool is_smime ();
/** @brief closes the inspector for a mail
*
* @returns true on success.
*/
static int close_inspector (Mail *mail);
/** @brief get the associated parser.
only valid while the actual parsing happens. */
std::shared_ptr<ParseController> parser () { return m_parser; }
/** To be called from outside once the paser was done.
In Qt this would be a slot that is called once it is finished
we hack around that a bit by calling it from our windowmessages
handler.
*/
void parsing_done ();
/** Returns true if the mail was verified and has at least one
signature. Regardless of the validity of the mail */
bool is_signed () const;
/** Returns true if the mail is encrypted to at least one
recipient. Regardless if it could be decrypted. */
bool is_encrypted () const;
/** Are we "green" */
bool is_valid_sig ();
/** Get UID gets UniqueID property of this mail. Returns
an empty string if the uid was not set with set uid.*/
const std::string & get_uuid () const { return m_uuid; }
/** Returns 0 on success if the mail has a uid alrady or sets
the uid. Setting only succeeds if the OOM is currently
accessible. Returns -1 on error. */
int set_uuid ();
/** Returns a localized string describing in one or two
words the crypto status of this mail. */
std::string get_crypto_summary ();
/** Returns a localized string describing the detailed
crypto state of this mail. */
std::string get_crypto_details ();
/** Returns a localized string describing a one line
summary of the crypto state. */
std::string get_crypto_one_line ();
/** Get the icon id of the appropiate icon for this mail */
int get_crypto_icon_id () const;
/** Get the fingerprint of an associated signature or null
if it is not signed. */
const char *get_sig_fpr() const;
/** Remove all categories of this mail */
void remove_categories ();
/** Get the body of the mail */
std::string get_body () const;
/** Get the html of the mail */
std::string get_html_body () const;
/** Get the recipients recipients is a null
terminated array of strings. Needs to be freed
by the caller. */
char ** get_recipients () const;
/** Call close with discard changes to discard
plaintext. returns the value of the oom close
call. This may have delete the mail if the close
triggers an unload.
*/
static int close (Mail *mail);
/** Try to locate the keys for all recipients */
void locate_keys();
/** State variable to check if a close was triggerd by us. */
void set_close_triggered (bool value);
bool get_close_triggered () const;
/** Check if the mail should be sent as html alternative mail.
Only valid if update_oom_data was called before. */
bool is_html_alternative () const;
/** Get the html body. It is updated in update_oom_data. */
const std::string & get_cached_html_body () const;
/** Returns 1 if the mail was encrypted, 2 if signed, 3 if both.
Only valid after decrypt_verify.
*/
int get_crypto_flags () const;
/** Returns true if the mail should be encrypted in the
after write event. */
bool needs_encrypt () const;
void set_needs_encrypt (bool val);
/** Gets the level of the signature. See:
https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption for
a definition of the levels. */
int get_signature_level () const;
/** Check if all attachments are hidden and show a warning
message appropiate to the crypto state if necessary. */
int check_attachments () const;
/** Check if the mail should be encrypted "inline" */
bool should_inline_crypt () const {return m_do_inline;}
private:
void update_categories ();
void update_body ();
void update_sigstate ();
LPDISPATCH m_mailitem;
LPDISPATCH m_event_sink;
bool m_processed, /* The message has been porcessed by us. */
m_needs_wipe, /* We have added plaintext to the mesage. */
m_needs_save, /* A property was changed but not by us. */
m_crypt_successful, /* We successfuly performed crypto on the item. */
m_is_smime, /* This is an smime mail. */
m_is_smime_checked, /* it was checked if this is an smime mail */
m_is_signed, /* Mail is signed */
m_is_valid, /* Mail is valid signed. */
m_close_triggered, /* We have programtically triggered a close */
m_is_html_alternative, /* Body Format is not plain text */
m_needs_encrypt; /* Send was triggered we want to encrypt. */
int m_moss_position; /* The number of the original message attachment. */
int m_crypto_flags;
std::string m_sender;
std::string m_html_body; /* Cached html body. */
msgtype_t m_type; /* Our messagetype as set in mapi */
std::shared_ptr <ParseController> m_parser;
GpgME::VerificationResult m_verify_result;
GpgME::DecryptionResult m_decrypt_result;
GpgME::Signature m_sig;
GpgME::UserID m_uid;
std::string m_uuid;
std::string m_orig_body;
bool m_do_inline;
+ bool m_is_gsuite; /* Are we on a gsuite account */
};
#endif // MAIL_H
diff --git a/src/mimemaker.cpp b/src/mimemaker.cpp
index 50d2485..ba9bddd 100644
--- a/src/mimemaker.cpp
+++ b/src/mimemaker.cpp
@@ -1,2369 +1,2369 @@
/* mimemaker.c - Construct MIME message out of a MAPI
* Copyright (C) 2007, 2008 g10 Code GmbH
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#define COBJMACROS
#include <windows.h>
#include <objidl.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "engine.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "oomhelp.h"
#include "gpgolstr.h"
#include "mail.h"
static const unsigned char oid_mimetag[] =
{0x2A, 0x86, 0x48, 0x86, 0xf7, 0x14, 0x03, 0x0a, 0x04};
/* The base-64 list used for base64 encoding. */
static unsigned char bintoasc[64+1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/");
/* Object used to collect data in a memory buffer. */
struct databuf_s
{
size_t len; /* Used length. */
size_t size; /* Allocated length of BUF. */
char *buf; /* Malloced buffer. */
};
/*** local prototypes ***/
static int write_multistring (sink_t sink, const char *text1,
...) GPGOL_GCC_A_SENTINEL(0);
/* Standard write method used with a sink_t object. */
int
sink_std_write (sink_t sink, const void *data, size_t datalen)
{
HRESULT hr;
LPSTREAM stream = static_cast<LPSTREAM>(sink->cb_data);
if (!stream)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
if (!data)
return 0; /* Flush - nothing to do here. */
hr = stream->Write(data, datalen, NULL);
if (hr)
{
log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
return -1;
}
return 0;
}
/* Write method used with a sink_t that contains a file object. */
int
sink_file_write (sink_t sink, const void *data, size_t datalen)
{
HANDLE hFile = sink->cb_data;
DWORD written = 0;
if (!hFile || hFile == INVALID_HANDLE_VALUE)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
if (!data)
return 0; /* Flush - nothing to do here. */
if (!WriteFile (hFile, data, datalen, &written, NULL))
{
log_error ("%s:%s: Write failed: ", SRCNAME, __func__);
return -1;
}
return 0;
}
/* Make sure that PROTOCOL is usable or return a suitable protocol.
On error PROTOCOL_UNKNOWN is returned. */
static protocol_t
check_protocol (protocol_t protocol)
{
switch (protocol)
{
case PROTOCOL_UNKNOWN:
return PROTOCOL_UNKNOWN;
case PROTOCOL_OPENPGP:
case PROTOCOL_SMIME:
return protocol;
}
log_error ("%s:%s: BUG", SRCNAME, __func__);
return PROTOCOL_UNKNOWN;
}
/* Create a new MAPI attchment for MESSAGE which will be used to
prepare the MIME message. On sucess the stream to write the data
to is stored at STREAM and the attachment object itself is
returned. The caller needs to call SaveChanges. Returns NULL on
failure in which case STREAM will be set to NULL. */
static LPATTACH
create_mapi_attachment (LPMESSAGE message, sink_t sink)
{
HRESULT hr;
ULONG pos;
SPropValue prop;
LPATTACH att = NULL;
LPUNKNOWN punk;
sink->cb_data = NULL;
sink->writefnc = NULL;
hr = message->CreateAttach(NULL, 0, &pos, &att);
if (hr)
{
log_error ("%s:%s: can't create attachment: hr=%#lx\n",
SRCNAME, __func__, hr);
return NULL;
}
prop.ulPropTag = PR_ATTACH_METHOD;
prop.Value.ul = ATTACH_BY_VALUE;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (hr)
{
log_error ("%s:%s: can't set attach method: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
/* Mark that attachment so that we know why it has been created. */
if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
goto failure;
prop.Value.l = ATTACHTYPE_MOSSTEMPL;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Attach Type", hr);
goto failure;
}
/* We better insert a short filename. */
prop.ulPropTag = PR_ATTACH_FILENAME_A;
prop.Value.lpszA = strdup (MIMEATTACHFILENAME);
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
/* Even for encrypted messages we need to set the MAPI property to
multipart/signed. This seems to be a part of the trigger which
leads OL to process such a message in a special way. */
prop.ulPropTag = PR_ATTACH_TAG;
prop.Value.bin.cb = sizeof oid_mimetag;
prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (!hr)
{
prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
prop.Value.lpszA = strdup("multipart/signed");
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
xfree (prop.Value.lpszA);
}
if (hr)
{
log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
punk = NULL;
hr = att->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0,
(MAPI_CREATE|MAPI_MODIFY), &punk);
if (FAILED (hr))
{
log_error ("%s:%s: can't create output stream: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
sink->cb_data = (LPSTREAM)punk;
sink->writefnc = sink_std_write;
return att;
failure:
gpgol_release (att);
return NULL;
}
/* Write data to a sink_t. */
int
write_buffer (sink_t sink, const void *data, size_t datalen)
{
if (!sink || !sink->writefnc)
{
log_error ("%s:%s: sink not properly setup", SRCNAME, __func__);
return -1;
}
return sink->writefnc (sink, data, datalen);
}
/* Same as above but used for passing as callback function. This
fucntion does not return an error code but the number of bytes
written. */
int
write_buffer_for_cb (void *opaque, const void *data, size_t datalen)
{
sink_t sink = (sink_t) opaque;
sink->enc_counter += datalen;
return write_buffer (sink, data, datalen) ? -1 : datalen;
}
/* Write the string TEXT to the IStream STREAM. Returns 0 on sucsess,
prints an error message and returns -1 on error. */
static int
write_string (sink_t sink, const char *text)
{
return write_buffer (sink, text, strlen (text));
}
/* Write the string TEXT1 and all folloing arguments of type (const
char*) to the SINK. The list of argumens needs to be terminated
with a NULL. Returns 0 on sucsess, prints an error message and
returns -1 on error. */
static int
write_multistring (sink_t sink, const char *text1, ...)
{
va_list arg_ptr;
int rc;
const char *s;
va_start (arg_ptr, text1);
s = text1;
do
rc = write_string (sink, s);
while (!rc && (s=va_arg (arg_ptr, const char *)));
va_end (arg_ptr);
return rc;
}
/* Helper to write a boundary to the output sink. The leading LF
will be written as well. */
static int
write_boundary (sink_t sink, const char *boundary, int lastone)
{
int rc = write_string (sink, "\r\n--");
if (!rc)
rc = write_string (sink, boundary);
if (!rc)
rc = write_string (sink, lastone? "--\r\n":"\r\n");
return rc;
}
/* Write DATALEN bytes of DATA to SINK in base64 encoding. This
creates a complete Base64 chunk including the trailing fillers. */
static int
write_b64 (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
unsigned char inbuf[4];
int idx, quads;
char outbuf[2048];
size_t outlen;
log_debug (" writing base64 of length %d\n", (int)datalen);
idx = quads = 0;
outlen = 0;
for (p = (const unsigned char*)data; datalen; p++, datalen--)
{
inbuf[idx++] = *p;
if (idx > 2)
{
/* We need space for a quad and a possible CR,LF. */
if (outlen+4+2 >= sizeof outbuf)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
outlen = 0;
}
outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
|((inbuf[1] >> 4)&017))&077];
outbuf[outlen++] = bintoasc[(((inbuf[1]<<2)&074)
|((inbuf[2]>>6)&03))&077];
outbuf[outlen++] = bintoasc[inbuf[2]&077];
idx = 0;
if (++quads >= (64/4))
{
quads = 0;
outbuf[outlen++] = '\r';
outbuf[outlen++] = '\n';
}
}
}
/* We need space for a quad and a final CR,LF. */
if (outlen+4+2 >= sizeof outbuf)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
outlen = 0;
}
if (idx)
{
outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
if (idx == 1)
{
outbuf[outlen++] = bintoasc[((*inbuf<<4)&060)&077];
outbuf[outlen++] = '=';
outbuf[outlen++] = '=';
}
else
{
outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
|((inbuf[1]>>4)&017))&077];
outbuf[outlen++] = bintoasc[((inbuf[1]<<2)&074)&077];
outbuf[outlen++] = '=';
}
++quads;
}
if (quads)
{
outbuf[outlen++] = '\r';
outbuf[outlen++] = '\n';
}
if (outlen)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
}
return 0;
}
/* Write DATALEN bytes of DATA to SINK in quoted-prinable encoding. */
static int
write_qp (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
char outbuf[80]; /* We only need 76 octect + 2 for the lineend. */
int outidx;
/* Check whether the current character is followed by a line ending.
Note that the end of the etxt also counts as a lineending */
#define nextlf_p() ((datalen > 2 && p[1] == '\r' && p[2] == '\n') \
|| (datalen > 1 && p[1] == '\n') \
|| datalen == 1 )
/* Macro to insert a soft line break if needed. */
# define do_softlf(n) \
do { \
if (outidx + (n) > 76 \
|| (outidx + (n) == 76 && !nextlf_p())) \
{ \
outbuf[outidx++] = '='; \
outbuf[outidx++] = '\r'; \
outbuf[outidx++] = '\n'; \
if ((rc = write_buffer (sink, outbuf, outidx))) \
return rc; \
outidx = 0; \
} \
} while (0)
log_debug (" writing qp of length %d\n", (int)datalen);
outidx = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
/* Line break. */
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
outidx = 0;
if (*p == '\r')
{
p++;
datalen--;
}
}
else if (*p == '\t' || *p == ' ')
{
/* Check whether tab or space is followed by a line break
which forbids verbatim encoding. If we are already at
the end of the buffer we take that as a line end too. */
if (nextlf_p())
{
do_softlf (3);
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else
{
do_softlf (1);
outbuf[outidx++] = *p;
}
}
else if (!outidx && *p == '.' && nextlf_p () )
{
/* We better protect a line with just a single dot. */
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else if (!outidx && datalen >= 5 && !memcmp (p, "From ", 5))
{
/* Protect the 'F' so that MTAs won't prefix the "From "
with an '>' */
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else if (*p >= '!' && *p <= '~' && *p != '=')
{
do_softlf (1);
outbuf[outidx++] = *p;
}
else
{
do_softlf (3);
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
}
if (outidx)
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
}
# undef do_softlf
# undef nextlf_p
return 0;
}
/* Write DATALEN bytes of DATA to SINK in plain ascii encoding. */
static int
write_plain (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
char outbuf[100];
int outidx;
log_debug (" writing ascii of length %d\n", (int)datalen);
outidx = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
outidx = 0;
if (*p == '\r')
{
p++;
datalen--;
}
}
else if (!outidx && *p == '.'
&& ( (datalen > 2 && p[1] == '\r' && p[2] == '\n')
|| (datalen > 1 && p[1] == '\n')
|| datalen == 1))
{
/* Better protect a line with just a single dot. We do
this by adding a space. */
outbuf[outidx++] = *p;
outbuf[outidx++] = ' ';
}
else if (outidx > 80)
{
/* We should never be called for too long lines - QP should
have been used. */
log_error ("%s:%s: BUG: line longer than exepcted",
SRCNAME, __func__);
return -1;
}
else
outbuf[outidx++] = *p;
}
if (outidx)
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
}
return 0;
}
/* Infer the conent type from the FILENAME. The return value is
a static string there won't be an error return. In case Bae 64
encoding is required for the type true will be stored at FORCE_B64;
however, this is only a shortcut and if that is not set, the caller
should infer the encoding by other means. */
static const char *
infer_content_type (const char * /*data*/, size_t /*datalen*/,
const char *filename, int is_mapibody, int *force_b64)
{
static struct {
char b64;
const char *suffix;
const char *ct;
} suffix_table[] =
{
{ 1, "3gp", "video/3gpp" },
{ 1, "abw", "application/x-abiword" },
{ 1, "ai", "application/postscript" },
{ 1, "au", "audio/basic" },
{ 1, "bin", "application/octet-stream" },
{ 1, "class", "application/java-vm" },
{ 1, "cpt", "application/mac-compactpro" },
{ 0, "css", "text/css" },
{ 0, "csv", "text/comma-separated-values" },
{ 1, "deb", "application/x-debian-package" },
{ 1, "dl", "video/dl" },
{ 1, "doc", "application/msword" },
{ 1, "dv", "video/dv" },
{ 1, "dvi", "application/x-dvi" },
{ 1, "eml", "message/rfc822" },
{ 1, "eps", "application/postscript" },
{ 1, "fig", "application/x-xfig" },
{ 1, "flac", "application/x-flac" },
{ 1, "fli", "video/fli" },
{ 1, "gif", "image/gif" },
{ 1, "gl", "video/gl" },
{ 1, "gnumeric", "application/x-gnumeric" },
{ 1, "hqx", "application/mac-binhex40" },
{ 1, "hta", "application/hta" },
{ 0, "htm", "text/html" },
{ 0, "html", "text/html" },
{ 0, "ics", "text/calendar" },
{ 1, "jar", "application/java-archive" },
{ 1, "jpeg", "image/jpeg" },
{ 1, "jpg", "image/jpeg" },
{ 1, "js", "application/x-javascript" },
{ 1, "latex", "application/x-latex" },
{ 1, "lha", "application/x-lha" },
{ 1, "lzh", "application/x-lzh" },
{ 1, "lzx", "application/x-lzx" },
{ 1, "m3u", "audio/mpegurl" },
{ 1, "m4a", "audio/mpeg" },
{ 1, "mdb", "application/msaccess" },
{ 1, "midi", "audio/midi" },
{ 1, "mov", "video/quicktime" },
{ 1, "mp2", "audio/mpeg" },
{ 1, "mp3", "audio/mpeg" },
{ 1, "mp4", "video/mp4" },
{ 1, "mpeg", "video/mpeg" },
{ 1, "mpega", "audio/mpeg" },
{ 1, "mpg", "video/mpeg" },
{ 1, "mpga", "audio/mpeg" },
{ 1, "msi", "application/x-msi" },
{ 1, "mxu", "video/vnd.mpegurl" },
{ 1, "nb", "application/mathematica" },
{ 1, "oda", "application/oda" },
{ 1, "odb", "application/vnd.oasis.opendocument.database" },
{ 1, "odc", "application/vnd.oasis.opendocument.chart" },
{ 1, "odf", "application/vnd.oasis.opendocument.formula" },
{ 1, "odg", "application/vnd.oasis.opendocument.graphics" },
{ 1, "odi", "application/vnd.oasis.opendocument.image" },
{ 1, "odm", "application/vnd.oasis.opendocument.text-master" },
{ 1, "odp", "application/vnd.oasis.opendocument.presentation" },
{ 1, "ods", "application/vnd.oasis.opendocument.spreadsheet" },
{ 1, "odt", "application/vnd.oasis.opendocument.text" },
{ 1, "ogg", "application/ogg" },
{ 1, "otg", "application/vnd.oasis.opendocument.graphics-template" },
{ 1, "oth", "application/vnd.oasis.opendocument.text-web" },
{ 1, "otp", "application/vnd.oasis.opendocument.presentation-template"},
{ 1, "ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
{ 1, "ott", "application/vnd.oasis.opendocument.text-template" },
{ 1, "pdf", "application/pdf" },
{ 1, "png", "image/png" },
{ 1, "pps", "application/vnd.ms-powerpoint" },
{ 1, "ppt", "application/vnd.ms-powerpoint" },
{ 1, "prf", "application/pics-rules" },
{ 1, "ps", "application/postscript" },
{ 1, "qt", "video/quicktime" },
{ 1, "rar", "application/rar" },
{ 1, "rdf", "application/rdf+xml" },
{ 1, "rpm", "application/x-redhat-package-manager" },
{ 0, "rss", "application/rss+xml" },
{ 1, "ser", "application/java-serialized-object" },
{ 0, "sh", "application/x-sh" },
{ 0, "shtml", "text/html" },
{ 1, "sid", "audio/prs.sid" },
{ 0, "smil", "application/smil" },
{ 1, "snd", "audio/basic" },
{ 0, "svg", "image/svg+xml" },
{ 1, "tar", "application/x-tar" },
{ 0, "texi", "application/x-texinfo" },
{ 0, "texinfo", "application/x-texinfo" },
{ 1, "tif", "image/tiff" },
{ 1, "tiff", "image/tiff" },
{ 1, "torrent", "application/x-bittorrent" },
{ 1, "tsp", "application/dsptype" },
{ 0, "vrml", "model/vrml" },
{ 1, "vsd", "application/vnd.visio" },
{ 1, "wp5", "application/wordperfect5.1" },
{ 1, "wpd", "application/wordperfect" },
{ 0, "xhtml", "application/xhtml+xml" },
{ 1, "xlb", "application/vnd.ms-excel" },
{ 1, "xls", "application/vnd.ms-excel" },
{ 1, "xlt", "application/vnd.ms-excel" },
{ 0, "xml", "application/xml" },
{ 0, "xsl", "application/xml" },
{ 0, "xul", "application/vnd.mozilla.xul+xml" },
{ 1, "zip", "application/zip" },
{ 0, NULL, NULL }
};
int i;
std::string suffix;
*force_b64 = 0;
if (filename)
suffix = strrchr (filename, '.');
if (!suffix.empty())
{
suffix.erase(0, 1);
std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
for (i=0; suffix_table[i].suffix; i++)
{
if (!strcmp (suffix_table[i].suffix, suffix.c_str()))
{
if (suffix_table[i].b64)
*force_b64 = 1;
return suffix_table[i].ct;
}
}
}
/* Not found via filename, look at the content. */
if (is_mapibody == 1)
{
return "text/plain";
}
else if (is_mapibody == 2)
{
return "text/html";
}
return "application/octet-stream";
}
/* Figure out the best encoding to be used for the part. Return values are
0: Plain ASCII.
1: Quoted Printable
2: Base64 */
static int
infer_content_encoding (const void *data, size_t datalen)
{
const unsigned char *p;
int need_qp;
size_t len, maxlen, highbin, lowbin, ntotal;
ntotal = datalen;
len = maxlen = lowbin = highbin = 0;
need_qp = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
len++;
if ((*p & 0x80))
highbin++;
else if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
len--;
if (len > maxlen)
maxlen = len;
len = 0;
}
else if (*p == '\r')
{
/* CR not followed by a linefeed. */
lowbin++;
}
else if (*p == '\t' || *p == ' ' || *p == '\f')
;
else if (*p < ' ' || *p == 127)
lowbin++;
else if (len == 1 && datalen > 2
&& *p == '-' && p[1] == '-' && p[2] == ' '
&& ( (datalen > 4 && p[3] == '\r' && p[4] == '\n')
|| (datalen > 3 && p[3] == '\n')
|| datalen == 3))
{
/* This is a "-- \r\n" line, thus it indicates the usual
signature line delimiter. We need to protect the
trailing space. */
need_qp = 1;
}
else if (len == 1 && datalen > 5 && !memcmp (p, "--=-=", 5))
{
/* This look pretty much like a our own boundary.
We better protect it by forcing QP encoding. */
need_qp = 1;
}
else if (len == 1 && datalen >= 5 && !memcmp (p, "From ", 5))
{
/* The usual From hack is required so that MTAs do not
prefix it with an '>'. */
need_qp = 1;
}
}
if (len > maxlen)
maxlen = len;
if (maxlen <= 76 && !lowbin && !highbin && !need_qp)
return 0; /* Plain ASCII is sufficient. */
/* Somewhere in the Outlook documentation 20% is mentioned as
discriminating value for Base64. Though our counting won't be
identical we use that value to behave closely to it. */
if (ntotal && ((float)(lowbin+highbin))/ntotal < 0.20)
return 1; /* Use quoted printable. */
return 2; /* Use base64. */
}
/* Convert an utf8 input string to RFC2047 base64 encoding which
is the subset of RFC2047 outlook likes.
Return value needs to be freed.
*/
static char *
utf8_to_rfc2047b (const char *input)
{
char *ret,
*encoded;
int inferred_encoding = 0;
if (!input)
{
return NULL;
}
inferred_encoding = infer_content_encoding (input, strlen (input));
if (!inferred_encoding)
{
return xstrdup (input);
}
log_debug ("%s:%s: Encoding attachment filename. With: %s ",
SRCNAME, __func__, inferred_encoding == 2 ? "Base64" : "QP");
if (inferred_encoding == 2)
{
encoded = b64_encode (input, strlen (input));
if (gpgrt_asprintf (&ret, "=?utf-8?B?%s?=", encoded) == -1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
xfree (encoded);
return NULL;
}
}
else
{
/* There is a Bug here. If you encode 4 Byte UTF-8 outlook can't
handle it itself. And sends out a message with ?? inserted in
that place. This triggers an invalid signature. */
encoded = qp_encode (input, strlen (input), NULL);
if (gpgrt_asprintf (&ret, "=?utf-8?Q?%s?=", encoded) == -1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
xfree (encoded);
return NULL;
}
}
xfree (encoded);
return ret;
}
/* Write a MIME part to SINK. First the BOUNDARY is written (unless
it is NULL) then the DATA is analyzed and appropriate headers are
written. If FILENAME is given it will be added to the part's
header. IS_MAPIBODY should be passed as true if the data has been
retrieved from the body property. */
static int
write_part (sink_t sink, const char *data, size_t datalen,
const char *boundary, const char *filename, int is_mapibody,
const char *content_id = NULL)
{
int rc;
const char *ct;
int use_b64, use_qp, is_text;
char *encoded_filename;
if (filename)
{
/* If there is a filename strip the directory part. Take care
that there might be slashes or backslashes. */
const char *s1 = strrchr (filename, '/');
const char *s2 = strrchr (filename, '\\');
if (!s1)
s1 = s2;
else if (s1 && s2 && s2 > s1)
s1 = s2;
if (s1)
filename = s1;
if (*filename && filename[1] == ':')
filename += 2;
if (!*filename)
filename = NULL;
}
log_debug ("Writing part of length %d%s filename=`%s'\n",
(int)datalen, is_mapibody? " (body)":"",
filename?filename:"[none]");
ct = infer_content_type (data, datalen, filename, is_mapibody, &use_b64);
use_qp = 0;
if (!use_b64)
{
switch (infer_content_encoding (data, datalen))
{
case 0: break;
case 1: use_qp = 1; break;
default: use_b64 = 1; break;
}
}
is_text = !strncmp (ct, "text/", 5);
if (boundary)
if ((rc = write_boundary (sink, boundary, 0)))
return rc;
if ((rc=write_multistring (sink,
"Content-Type: ", ct,
(is_text || filename? ";\r\n" :"\r\n"),
NULL)))
return rc;
/* OL inserts a charset parameter in many cases, so we do it right
away for all text parts. We can assume us-ascii if no special
encoding is required. */
if (is_text)
if ((rc=write_multistring (sink,
"\tcharset=\"",
(!use_qp && !use_b64? "us-ascii" : "utf-8"),
filename ? "\";\r\n" : "\"\r\n",
NULL)))
return rc;
encoded_filename = utf8_to_rfc2047b (filename);
if (encoded_filename)
if ((rc=write_multistring (sink,
"\tname=\"", encoded_filename, "\"\r\n",
NULL)))
return rc;
/* Note that we need to output even 7bit because OL inserts that
anyway. */
if ((rc = write_multistring (sink,
"Content-Transfer-Encoding: ",
(use_b64? "base64\r\n":
use_qp? "quoted-printable\r\n":"7bit\r\n"),
NULL)))
return rc;
if (content_id)
{
if ((rc=write_multistring (sink,
"Content-ID: <", content_id, ">\r\n",
NULL)))
return rc;
}
else if (encoded_filename)
if ((rc=write_multistring (sink,
"Content-Disposition: attachment;\r\n"
"\tfilename=\"", encoded_filename, "\"\r\n",
NULL)))
return rc;
xfree(encoded_filename);
/* Write delimiter. */
if ((rc = write_string (sink, "\r\n")))
return rc;
/* Write the content. */
if (use_b64)
rc = write_b64 (sink, data, datalen);
else if (use_qp)
rc = write_qp (sink, data, datalen);
else
rc = write_plain (sink, data, datalen);
return rc;
}
/* Return the number of attachments in TABLE to be put into the MIME
message. */
-static int
+int
count_usable_attachments (mapi_attach_item_t *table)
{
int idx, count = 0;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
&& table[idx].method == ATTACH_BY_VALUE)
count++;
return count;
}
/* Write out all attachments from TABLE separated by BOUNDARY to SINK.
This function needs to be syncronized with count_usable_attachments.
If only_related is 1 only include attachments for multipart/related they
are excluded otherwise. */
static int
write_attachments (sink_t sink,
LPMESSAGE message, mapi_attach_item_t *table,
const char *boundary, int only_related)
{
int idx, rc;
char *buffer;
size_t buflen;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
&& table[idx].method == ATTACH_BY_VALUE)
{
if (only_related && !table[idx].content_id)
{
continue;
}
else if (!only_related && table[idx].content_id)
{
continue;
}
buffer = mapi_get_attach (message, 0, table+idx, &buflen);
if (!buffer)
log_debug ("Attachment at index %d not found\n", idx);
else
log_debug ("Attachment at index %d: length=%d\n", idx, (int)buflen);
if (!buffer)
return -1;
rc = write_part (sink, buffer, buflen, boundary,
table[idx].filename, 0, table[idx].content_id);
if (rc)
{
log_error ("Write part returned err: %i", rc);
}
xfree (buffer);
}
return 0;
}
/* Returns 1 if all attachments are related. 2 if there is a
related and a mixed attachment. 0 if there are no other parts*/
static int
is_related (Mail *mail, mapi_attach_item_t *table)
{
if (!mail || !mail->is_html_alternative () || !table)
{
return 0;
}
int related = 0;
int mixed = 0;
for (int idx = 0; !table[idx].end_of_table; idx++)
{
if (table[idx].content_id)
{
related = 1;
}
else
{
mixed = 1;
}
}
return mixed + related;
}
/* Delete all attachments from TABLE except for the one we just created */
static int
delete_all_attachments (LPMESSAGE message, mapi_attach_item_t *table)
{
HRESULT hr;
int idx;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
{
if (table[idx].attach_type == ATTACHTYPE_MOSSTEMPL
&& table[idx].filename
&& !strcmp (table[idx].filename, MIMEATTACHFILENAME))
continue;
hr = message->DeleteAttach (table[idx].mapipos, 0, NULL, 0);
if (hr)
{
log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
}
return 0;
}
/* Commit changes to the attachment ATTACH and release the object.
SINK needs to be passed as well and will also be closed. Note that
the address of ATTACH is expected so that the fucntion can set it
to NULL. */
static int
close_mapi_attachment (LPATTACH *attach, sink_t sink)
{
HRESULT hr;
LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
if (!stream)
{
log_error ("%s:%s: sink not setup", SRCNAME, __func__);
return -1;
}
hr = stream->Commit (0);
if (hr)
{
log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
SRCNAME, __func__, hr);
return -1;
}
gpgol_release (stream);
sink->cb_data = NULL;
hr = (*attach)->SaveChanges (0);
if (hr)
{
log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
gpgol_release ((*attach));
*attach = NULL;
return 0;
}
/* Cancel changes to the attachment ATTACH and release the object.
SINK needs to be passed as well and will also be closed. Note that
the address of ATTACH is expected so that the fucntion can set it
to NULL. */
static void
cancel_mapi_attachment (LPATTACH *attach, sink_t sink)
{
LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
if (stream)
{
stream->Revert();
gpgol_release (stream);
sink->cb_data = NULL;
}
if (*attach)
{
/* Fixme: Should we try to delete it or is there a Revert method? */
gpgol_release ((*attach));
*attach = NULL;
}
}
/* Do the final processing for a message. */
static int
finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
protocol_t protocol, int encrypt, bool is_inline = false)
{
HRESULT hr;
SPropValue prop;
SPropTagArray proparray;
/* Set the message class. */
prop.ulPropTag = PR_MESSAGE_CLASS_A;
if (encrypt)
{
prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned");
}
else
{
prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOLS.SMIME.MultipartSigned");
}
hr = message->SetProps(1, &prop, NULL);
xfree(prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: error setting the message class: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
/* Set a special property so that we are later able to identify
messages signed or encrypted by us. */
if (mapi_set_sig_status (message, "@"))
{
log_error ("%s:%s: error setting sigstatus",
SRCNAME, __func__);
return -1;
}
/* We also need to set the message class into our custom
property. This override is at least required for encrypted
messages. */
if (is_inline && mapi_set_gpgol_msg_class (message,
(encrypt?
(protocol == PROTOCOL_SMIME?
"IPM.Note.GpgOL.OpaqueEncrypted" :
"IPM.Note.GpgOL.PGPMessage") :
"IPM.Note.GpgOL.ClearSigned")))
{
log_error ("%s:%s: error setting gpgol msgclass",
SRCNAME, __func__);
return -1;
}
if (!is_inline && mapi_set_gpgol_msg_class (message,
(encrypt?
(protocol == PROTOCOL_SMIME?
"IPM.Note.GpgOL.OpaqueEncrypted" :
"IPM.Note.GpgOL.MultipartEncrypted") :
"IPM.Note.GpgOL.MultipartSigned")))
{
log_error ("%s:%s: error setting gpgol msgclass",
SRCNAME, __func__);
return -1;
}
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
{
log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
SRCNAME, __func__);
}
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY_HTML;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
{
log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
SRCNAME, __func__);
}
/* Now delete all parts of the MAPI message except for the one
attachment we just created. */
if (delete_all_attachments (message, att_table))
{
log_error ("%s:%s: error deleting attachments",
SRCNAME, __func__);
return -1;
}
/* Remove the draft info so that we don't leak the information on
whether the message has been signed etc. */
mapi_set_gpgol_draft_info (message, NULL);
if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
{
log_error ("%s:%s: error saving changes.",
SRCNAME, __func__);
return -1;
}
return 0;
}
/* Sink write method used by mime_sign. We write the data to the
filter and also to the EXTRASINK but we don't pass a flush request
to EXTRASINK. */
static int
sink_hashing_write (sink_t hashsink, const void *data, size_t datalen)
{
int rc;
engine_filter_t filter = (engine_filter_t) hashsink->cb_data;
if (!filter || !hashsink->extrasink)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
rc = engine_filter (filter, data, datalen);
if (!rc && data && datalen)
write_buffer (hashsink->extrasink, data, datalen);
return rc;
}
/* This function is called by the filter to collect the output which
is a detached signature. */
static int
collect_signature (void *opaque, const void *data, size_t datalen)
{
struct databuf_s *db = (databuf_s *)opaque;
if (db->len + datalen >= db->size)
{
db->size += datalen + 1024;
db->buf = (char*) xrealloc (db->buf, db->size);
}
memcpy (db->buf + db->len, data, datalen);
db->len += datalen;
return datalen;
}
/* Helper to create the signing header. This includes enough space
for later fixup of the micalg parameter. The MIME version is only
written if FIRST is set. */
static void
create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
int first, const char *boundary, const char *micalg)
{
snprintf (buffer, buflen,
"%s"
"Content-Type: multipart/signed;\r\n"
"\tprotocol=\"application/%s\";\r\n"
"\tmicalg=%-15.15s;\r\n"
"\tboundary=\"%s\"\r\n"
"\r\n",
first? "MIME-Version: 1.0\r\n":"",
(protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
micalg, boundary);
}
/* Add the body, either as multipart/alternative or just as the
simple body part. Depending on the format set in outlook. To
avoid memory duplication it takes the plain body as parameter.
Boundary is the potential outer boundary of a multipart/mixed
mail. If it is null we assume the multipart/alternative is
the only part.
return is zero on success.
*/
static int
add_body (Mail *mail, const char *boundary, sink_t sink,
const char *plain_body)
{
if (!plain_body)
{
return 0;
}
bool is_alternative = false;
if (mail)
{
is_alternative = mail->is_html_alternative ();
}
int rc = 0;
if (!is_alternative || !plain_body)
{
if (plain_body)
{
rc = write_part (sink, plain_body, strlen (plain_body),
boundary, NULL, 1);
}
/* Just the plain body or no body. We are done. */
return rc;
}
/* Add a new multipart / mixed element. */
if (boundary && write_boundary (sink, boundary, 0))
{
TRACEPOINT;
return 1;
}
/* Now for the multipart/alternative part. We never do HTML only. */
char alt_boundary [BOUNDARYSIZE+1];
generate_boundary (alt_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/alternative;\r\n",
"\tboundary=\"", alt_boundary, "\"\r\n",
"\r\n", /* <-- extra line */
NULL)))
{
TRACEPOINT;
return rc;
}
/* Now the plain body part */
if ((rc = write_part (sink, plain_body, strlen (plain_body),
alt_boundary, NULL, 1)))
{
TRACEPOINT;
return rc;
}
/* Now the html body. It is somehow not accessible through PR_HTML,
OutlookSpy also shows MAPI Unsported (but shows the data) strange.
We just cache it. Memory is cheap :-) */
const auto html_body = mail->get_cached_html_body();
if (html_body.empty())
{
log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
SRCNAME, __func__);
}
rc = write_part (sink, html_body.c_str(), html_body.size(),
alt_boundary, NULL, 2);
if (rc)
{
TRACEPOINT;
return rc;
}
/* Finish our multipart */
return write_boundary (sink, alt_boundary, 1);
}
/* Add the body and attachments. Does multipart handling. */
static int
add_body_and_attachments (sink_t sink, LPMESSAGE message,
mapi_attach_item_t *att_table, Mail *mail,
const char *body, int n_att_usable)
{
int related = is_related (mail, att_table);
int rc = 0;
char inner_boundary[BOUNDARYSIZE+1];
char outer_boundary[BOUNDARYSIZE+1];
*outer_boundary = 0;
*inner_boundary = 0;
if (((body && n_att_usable) || n_att_usable > 1) && related == 1)
{
/* A body and at least one attachment or more than one attachment */
generate_boundary (outer_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/related;\r\n",
"\tboundary=\"", outer_boundary, "\"\r\n",
NULL)))
return rc;
}
else if ((body && n_att_usable) || n_att_usable > 1)
{
generate_boundary (outer_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/mixed;\r\n",
"\tboundary=\"", outer_boundary, "\"\r\n",
NULL)))
return rc;
}
/* Only one part. */
if (*outer_boundary && related == 2)
{
/* We have attachments that are related to the body and unrelated
attachments. So we need another part. */
if ((rc=write_boundary (sink, outer_boundary, 0)))
{
return rc;
}
generate_boundary (inner_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/related;\r\n",
"\tboundary=\"", inner_boundary, "\"\r\n",
NULL)))
{
return rc;
}
}
if ((rc=add_body (mail, *inner_boundary ? inner_boundary :
*outer_boundary ? outer_boundary : NULL,
sink, body)))
{
log_error ("%s:%s: Adding the body failed.",
SRCNAME, __func__);
return rc;
}
if (!rc && n_att_usable && related)
{
/* Write the related attachments. */
rc = write_attachments (sink, message, att_table,
*inner_boundary? inner_boundary :
*outer_boundary? outer_boundary : NULL, 1);
if (rc)
{
return rc;
}
/* Close the related part if neccessary.*/
if (*inner_boundary && (rc=write_boundary (sink, inner_boundary, 1)))
{
return rc;
}
}
/* Now write the other attachments */
if (!rc && n_att_usable)
rc = write_attachments (sink, message, att_table,
*outer_boundary? outer_boundary : NULL, 0);
/* Finish the possible multipart/mixed. */
if (*outer_boundary && (rc = write_boundary (sink, outer_boundary, 1)))
return rc;
return rc;
}
/* Main body of mime_sign without the the code to delete the original
attachments. On success the function returns the current
attachment table at R_ATT_TABLE or sets this to NULL on error. If
TMPSINK is set no attachment will be created but the output
written to that sink. */
static int
do_mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
mapi_attach_item_t **r_att_table, sink_t tmpsink,
unsigned int session_number, const char *sender,
Mail *mail)
{
int result = -1;
int rc;
LPATTACH attach = NULL;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
struct sink_s hashsinkmem;
sink_t hashsink = &hashsinkmem;
char boundary[BOUNDARYSIZE+1];
mapi_attach_item_t *att_table = NULL;
char *body = NULL;
int n_att_usable;
char top_header[BOUNDARYSIZE+200];
engine_filter_t filter = NULL;
struct databuf_s sigbuffer;
char *my_sender = NULL;
*r_att_table = NULL;
memset (sink, 0, sizeof *sink);
memset (hashsink, 0, sizeof *hashsink);
memset (&sigbuffer, 0, sizeof sigbuffer);
if (tmpsink)
{
attach = NULL;
sink = tmpsink;
}
else
{
attach = create_mapi_attachment (message, sink);
if (!attach)
return -1;
}
/* Get the attachment info and the body. */
body = mapi_get_body (message, NULL);
if (body && !*body)
{
xfree (body);
body = NULL;
}
att_table = mapi_create_attach_table (message, 0);
n_att_usable = count_usable_attachments (att_table);
if (!n_att_usable && !body)
{
log_debug ("%s:%s: can't sign an empty message\n", SRCNAME, __func__);
result = gpg_error (GPG_ERR_NO_DATA);
goto failure;
}
/* Prepare the signing. */
if (engine_create_filter (&filter, collect_signature, &sigbuffer))
goto failure;
if (session_number)
{
engine_set_session_number (filter, session_number);
{
char *tmp = mapi_get_subject (message);
engine_set_session_title (filter, tmp);
xfree (tmp);
}
}
if (sender)
my_sender = xstrdup (sender);
else
my_sender = mapi_get_sender (message);
if (engine_sign_start (filter, hwnd, protocol, my_sender, &protocol))
goto failure;
protocol = check_protocol (protocol);
if (protocol == PROTOCOL_UNKNOWN)
{
log_error ("%s:%s: no protocol selected", SRCNAME, __func__);
goto failure;
}
/* Write the top header. */
generate_boundary (boundary);
create_top_signing_header (top_header, sizeof top_header,
protocol, 1, boundary, "xxx");
if ((rc = write_string (sink, top_header)))
goto failure;
/* Write the boundary so that it is not included in the hashing. */
if ((rc = write_boundary (sink, boundary, 0)))
goto failure;
/* Create a new sink for hashing and write/hash our content. */
hashsink->cb_data = filter;
hashsink->extrasink = sink;
hashsink->writefnc = sink_hashing_write;
/* Add the plaintext */
if (add_body_and_attachments (hashsink, message, att_table, mail,
body, n_att_usable))
goto failure;
xfree (body);
body = NULL;
/* Here we are ready with the hashing. Flush the filter and wait
for the signing process to finish. */
if ((rc = write_buffer (hashsink, NULL, 0)))
goto failure;
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
hashsink->cb_data = NULL; /* Not needed anymore. */
/* Write signature attachment. */
if ((rc = write_boundary (sink, boundary, 0)))
goto failure;
if (protocol == PROTOCOL_OPENPGP)
{
rc = write_string (sink,
"Content-Type: application/pgp-signature\r\n");
}
else
{
rc = write_string (sink,
"Content-Transfer-Encoding: base64\r\n"
"Content-Type: application/pkcs7-signature\r\n");
/* rc = write_string (sink, */
/* "Content-Type: application/x-pkcs7-signature\r\n" */
/* "\tname=\"smime.p7s\"\r\n" */
/* "Content-Transfer-Encoding: base64\r\n" */
/* "Content-Disposition: attachment;\r\n" */
/* "\tfilename=\"smime.p7s\"\r\n"); */
}
/* About the above code:
If we would add "Content-Transfer-Encoding: 7bit\r\n" to this
attachment, Outlooks does not proceed with sending and even does
not return any error. A wild guess is that while OL adds this
header itself, it detects that it already exists and somehow gets
into a problem. It is not a problem with the other parts,
though. Hmmm, triggered by the top levels CT protocol parameter?
Anyway, it is not required that we add it as we won't hash it.
Note, that this only holds for OpenPGP; for S/MIME we need to set
set CTE. We even write it before the CT because that is the same
as Outlook would do it for a missing CTE. */
if (rc)
goto failure;
if ((rc = write_string (sink, "\r\n")))
goto failure;
/* Write the signature. We add an extra CR,LF which should not harm
and a terminating 0. */
collect_signature (&sigbuffer, "\r\n", 3);
if ((rc = write_string (sink, sigbuffer.buf)))
goto failure;
/* Write the final boundary and finish the attachment. */
if ((rc = write_boundary (sink, boundary, 1)))
goto failure;
/* Fixup the micalg parameter. */
{
HRESULT hr;
LARGE_INTEGER off;
LPSTREAM stream = (LPSTREAM) sink->cb_data;
off.QuadPart = 0;
hr = stream->Seek (off, STREAM_SEEK_SET, NULL);
if (hr)
{
log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
SRCNAME, __func__, hr);
goto failure;
}
create_top_signing_header (top_header, sizeof top_header,
protocol, 1, boundary,
protocol == PROTOCOL_SMIME? "sha1":"pgp-sha1");
hr = stream->Write (top_header, strlen (top_header), NULL);
if (hr)
{
log_error ("%s:%s: writing fixed micalg failed: hr=%#lx",
SRCNAME, __func__, hr);
goto failure;
}
/* Better seek again to the end. */
off.QuadPart = 0;
hr = stream->Seek (off, STREAM_SEEK_END, NULL);
if (hr)
{
log_error ("%s:%s: seeking back to the end failed: hr=%#lx",
SRCNAME, __func__, hr);
goto failure;
}
}
if (attach)
{
if (close_mapi_attachment (&attach, sink))
goto failure;
}
result = 0; /* Everything is fine, fall through the cleanup now. */
failure:
engine_cancel (filter);
if (attach)
cancel_mapi_attachment (&attach, sink);
xfree (body);
if (result)
mapi_release_attach_table (att_table);
else
*r_att_table = att_table;
xfree (sigbuffer.buf);
xfree (my_sender);
return result;
}
/* Sign the MESSAGE using PROTOCOL. If PROTOCOL is PROTOCOL_UNKNOWN
the engine decides what protocol to use. On return MESSAGE is
modified so that sending it will result in a properly MOSS (that is
PGP or S/MIME) signed message. On failure the function tries to
keep the original message intact but there is no 100% guarantee for
it. */
int
mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
const char *sender, Mail *mail)
{
int result = -1;
mapi_attach_item_t *att_table;
result = do_mime_sign (message, hwnd, protocol, &att_table, 0,
engine_new_session_number (), sender, mail);
if (!result)
{
if (!finalize_message (message, att_table, protocol, 0))
result = 0;
}
mapi_release_attach_table (att_table);
return result;
}
/* Sink write method used by mime_encrypt. */
int
sink_encryption_write (sink_t encsink, const void *data, size_t datalen)
{
engine_filter_t filter = (engine_filter_t) encsink->cb_data;
if (!filter)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
return engine_filter (filter, data, datalen);
}
#if 0 /* Not used. */
/* Sink write method used by mime_encrypt for writing Base64. */
static int
sink_encryption_write_b64 (sink_t encsink, const void *data, size_t datalen)
{
engine_filter_t filter = encsink->cb_data;
int rc;
const unsigned char *p;
unsigned char inbuf[4];
int idx, quads;
char outbuf[6];
size_t outbuflen;
if (!filter)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
idx = encsink->b64.idx;
assert (idx < 4);
memcpy (inbuf, encsink->b64.inbuf, idx);
quads = encsink->b64.quads;
if (!data) /* Flush. */
{
outbuflen = 0;
if (idx)
{
outbuf[0] = bintoasc[(*inbuf>>2)&077];
if (idx == 1)
{
outbuf[1] = bintoasc[((*inbuf<<4)&060)&077];
outbuf[2] = '=';
outbuf[3] = '=';
}
else
{
outbuf[1] = bintoasc[(((*inbuf<<4)&060)|
((inbuf[1]>>4)&017))&077];
outbuf[2] = bintoasc[((inbuf[1]<<2)&074)&077];
outbuf[3] = '=';
}
outbuflen = 4;
quads++;
}
if (quads)
{
outbuf[outbuflen++] = '\r';
outbuf[outbuflen++] = '\n';
}
if (outbuflen && (rc = engine_filter (filter, outbuf, outbuflen)))
return rc;
/* Send the flush command to the filter. */
if ((rc = engine_filter (filter, data, datalen)))
return rc;
}
else
{
for (p = data; datalen; p++, datalen--)
{
inbuf[idx++] = *p;
if (idx > 2)
{
idx = 0;
outbuf[0] = bintoasc[(*inbuf>>2)&077];
outbuf[1] = bintoasc[(((*inbuf<<4)&060)
|((inbuf[1] >> 4)&017))&077];
outbuf[2] = bintoasc[(((inbuf[1]<<2)&074)
|((inbuf[2]>>6)&03))&077];
outbuf[3] = bintoasc[inbuf[2]&077];
outbuflen = 4;
if (++quads >= (64/4))
{
quads = 0;
outbuf[4] = '\r';
outbuf[5] = '\n';
outbuflen += 2;
}
if ((rc = engine_filter (filter, outbuf, outbuflen)))
return rc;
}
}
}
encsink->b64.idx = idx;
memcpy (encsink->b64.inbuf, inbuf, idx);
encsink->b64.quads = quads;
return 0;
}
#endif /*Not used.*/
/* Helper from mime_encrypt. BOUNDARY is a buffer of at least
BOUNDARYSIZE+1 bytes which will be set on return from that
function. */
static int
create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
bool is_inline = false)
{
int rc;
if (is_inline)
{
*boundary = 0;
rc = write_multistring (sink,
"MIME-Version: 1.0\r\n"
"Content-Type: text/plain;\r\n"
"\tcharset=\"iso-8859-1\"\r\n"
"Content-Transfer-Encoding: 7BIT\r\n"
"\r\n",
NULL);
}
else if (protocol == PROTOCOL_SMIME)
{
*boundary = 0;
rc = write_multistring (sink,
"MIME-Version: 1.0\r\n"
"Content-Type: application/pkcs7-mime;\r\n"
"\tsmime-type=enveloped-data;\r\n"
"\tname=\"smime.p7m\"\r\n"
"Content-Transfer-Encoding: base64\r\n"
"\r\n",
NULL);
}
else
{
generate_boundary (boundary);
rc = write_multistring (sink,
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/encrypted;\r\n"
"\tprotocol=\"application/pgp-encrypted\";\r\n",
"\tboundary=\"", boundary, "\"\r\n",
NULL);
if (rc)
return rc;
/* Write the PGP/MIME encrypted part. */
rc = write_boundary (sink, boundary, 0);
if (rc)
return rc;
rc = write_multistring (sink,
"Content-Type: application/pgp-encrypted\r\n"
"\r\n"
"Version: 1\r\n", NULL);
if (rc)
return rc;
/* And start the second part. */
rc = write_boundary (sink, boundary, 0);
if (rc)
return rc;
rc = write_multistring (sink,
"Content-Type: application/octet-stream\r\n"
"\r\n", NULL);
}
return rc;
}
/* Encrypt the MESSAGE. */
int
mime_encrypt (LPMESSAGE message, HWND hwnd,
protocol_t protocol, char **recipients,
const char *sender, Mail* mail)
{
int result = -1;
int rc;
LPATTACH attach;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
struct sink_s encsinkmem;
sink_t encsink = &encsinkmem;
char boundary[BOUNDARYSIZE+1];
mapi_attach_item_t *att_table = NULL;
char *body = NULL;
int n_att_usable;
engine_filter_t filter = NULL;
char *my_sender = NULL;
bool is_inline = mail && mail->should_inline_crypt ();
memset (sink, 0, sizeof *sink);
memset (encsink, 0, sizeof *encsink);
attach = create_mapi_attachment (message, sink);
if (!attach)
return -1;
/* Get the attachment info and the body. We need to do this before
creating the engine's filter sue problem sending the cancel to
the engine with nothing for the engine to process. This is
actually a bug in our engine code but we better avoid triggering
this bug because the engine sometimes hangs. Fixme: Needs a
proper fix. */
body = mapi_get_body (message, NULL);
if (body && !*body)
{
xfree (body);
body = NULL;
}
att_table = mapi_create_attach_table (message, 0);
n_att_usable = count_usable_attachments (att_table);
if (!n_att_usable && !body)
{
log_debug ("%s:%s: can't encrypt an empty message\n", SRCNAME, __func__);
result = gpg_error (GPG_ERR_NO_DATA);
goto failure;
}
if (n_att_usable && is_inline)
{
log_debug ("%s:%s: PGP Inline not supported for attachments. Using PGP MIME",
SRCNAME, __func__);
is_inline = false;
}
/* Prepare the encryption. We do this early as it is quite common
that some recipient keys are not available and thus the
encryption will fail early. */
if (engine_create_filter (&filter, write_buffer_for_cb, sink))
goto failure;
engine_set_session_number (filter, engine_new_session_number ());
{
char *tmp = mapi_get_subject (message);
engine_set_session_title (filter, tmp);
xfree (tmp);
}
if (sender)
my_sender = xstrdup (sender);
else
my_sender = mapi_get_sender (message);
if (engine_encrypt_prepare (filter, hwnd, protocol, 0,
my_sender, recipients, &protocol))
goto failure;
if (engine_encrypt_start (filter, 0))
goto failure;
protocol = check_protocol (protocol);
if (protocol == PROTOCOL_UNKNOWN)
goto failure;
if (protocol != PROTOCOL_OPENPGP)
{
log_debug ("%s:%s: Inline not supported for S/MIME. Using MIME",
SRCNAME, __func__);
is_inline = false;
}
/* Write the top header. */
rc = create_top_encryption_header (sink, protocol, boundary,
is_inline);
if (rc)
goto failure;
/* Create a new sink for encrypting the following stuff. */
encsink->cb_data = filter;
encsink->writefnc = sink_encryption_write;
/* Add the plaintext */
if (is_inline && body)
{
if ((rc = write_multistring (encsink, body, NULL)))
{
log_error ("%s:%s: Adding the body failed.",
SRCNAME, __func__);
goto failure;
}
}
else if (add_body_and_attachments (encsink, message, att_table, mail,
body, n_att_usable))
{
goto failure;
}
xfree (body);
body = NULL;
/* Flush the encryption sink and wait for the encryption to get
ready. */
if ((rc = write_buffer (encsink, NULL, 0)))
goto failure;
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
encsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Write the final boundary (for OpenPGP) and finish the attachment. */
if (*boundary && (rc = write_boundary (sink, boundary, 1)))
goto failure;
if (close_mapi_attachment (&attach, sink))
goto failure;
if (finalize_message (message, att_table, protocol, 1, is_inline))
goto failure;
result = 0; /* Everything is fine, fall through the cleanup now. */
failure:
engine_cancel (filter);
cancel_mapi_attachment (&attach, sink);
xfree (body);
mapi_release_attach_table (att_table);
xfree (my_sender);
return result;
}
/* Sign and Encrypt the MESSAGE. */
int
mime_sign_encrypt (LPMESSAGE message, HWND hwnd,
protocol_t protocol, char **recipients,
const char *sender, Mail *mail)
{
int result = -1;
int rc = 0;
HRESULT hr;
LPATTACH attach;
LPSTREAM tmpstream = NULL;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
struct sink_s encsinkmem;
sink_t encsink = &encsinkmem;
struct sink_s tmpsinkmem;
sink_t tmpsink = &tmpsinkmem;
char boundary[BOUNDARYSIZE+1];
mapi_attach_item_t *att_table = NULL;
engine_filter_t filter = NULL;
unsigned int session_number;
char *my_sender = NULL;
memset (sink, 0, sizeof *sink);
memset (encsink, 0, sizeof *encsink);
memset (tmpsink, 0, sizeof *tmpsink);
attach = create_mapi_attachment (message, sink);
if (!attach)
return -1;
/* First check that we are not trying to process an empty message
which might lock up our engine. Unfortunately we need to
duplicate the code we use in do_mime_sign here. FIXME: The
engine should be fixed instead of using such a workaround. */
{
char *body;
body = mapi_get_body (message, NULL);
if (body && !*body)
{
xfree (body);
body = NULL;
}
att_table = mapi_create_attach_table (message, 0);
if (!count_usable_attachments (att_table) && !body)
result = gpg_error (GPG_ERR_NO_DATA);
xfree (body);
if (att_table)
{
mapi_release_attach_table (att_table);
att_table = NULL;
}
if (gpg_err_code (result) == GPG_ERR_NO_DATA)
{
log_debug ("%s:%s: can't sign+encrypt an empty message\n",
SRCNAME, __func__);
goto failure;
}
}
/* Create a temporary sink to construct the signed data. */
hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
(SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
| STGM_CREATE | STGM_READWRITE),
NULL, GpgOLStr("GPG"), &tmpstream);
if (FAILED (hr))
{
log_error ("%s:%s: can't create temp file: hr=%#lx\n",
SRCNAME, __func__, hr);
rc = -1;
goto failure;
}
tmpsink->cb_data = tmpstream;
tmpsink->writefnc = sink_std_write;
/* Prepare the encryption. We do this early as it is quite common
that some recipients are not available and thus the encryption
will fail early. This is also required to allow the UIserver to
figure out the protocol to use if we have not forced one. */
if (engine_create_filter (&filter, write_buffer_for_cb, sink))
goto failure;
session_number = engine_new_session_number ();
engine_set_session_number (filter, session_number);
{
char *tmp = mapi_get_subject (message);
engine_set_session_title (filter, tmp);
xfree (tmp);
}
if (sender)
my_sender = xstrdup (sender);
else
my_sender = mapi_get_sender (message);
if ((rc=engine_encrypt_prepare (filter, hwnd,
protocol, ENGINE_FLAG_SIGN_FOLLOWS,
my_sender, recipients, &protocol)))
goto failure;
protocol = check_protocol (protocol);
if (protocol == PROTOCOL_UNKNOWN)
goto failure;
/* Now sign the message. This creates another attachment with the
complete MIME object of the signed message. We can't do the
encryption in streaming mode while running the encryption because
we need to fix up that ugly micalg parameter after having created
the signature. Note that the protocol to use is taken from the
encryption operation. */
if (do_mime_sign (message, hwnd, protocol, &att_table, tmpsink,
session_number, sender, mail))
goto failure;
/* Now send the actual ENCRYPT command. This split up between
prepare and start is necessary to help with the implementarion of
the UI-server. If we would send the ENCRYPT command immediately
the UI-server might block while reading from the input stream
because we are first going to do a sign operation which in trun
needs the attention of the UI server. A more robust but
complicated approach to the UI-server would be to delay the
reading (and thus the start of the underlying encrypt operation)
until the first byte has been received. */
if ((rc=engine_encrypt_start (filter, 0)))
goto failure;
/* Write the top header. */
rc = create_top_encryption_header (sink, protocol, boundary);
if (rc)
goto failure;
/* Create a new sink for encrypting the temporary attachment with
the signed message. */
encsink->cb_data = filter;
encsink->writefnc = sink_encryption_write;
/* Copy the temporary stream to the encryption sink. */
{
LARGE_INTEGER off;
ULONG nread;
char buffer[4096];
off.QuadPart = 0;
hr = tmpstream->Seek (off, STREAM_SEEK_SET, NULL);
if (hr)
{
log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
for (;;)
{
hr = tmpstream->Read (buffer, sizeof buffer, &nread);
if (hr)
{
log_error ("%s:%s: IStream::Read failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
if (!nread)
break; /* EOF */
rc = write_buffer (encsink, buffer, nread);
if (rc)
{
log_error ("%s:%s: writing tmpstream to encsink failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
}
}
/* Flush the encryption sink and wait for the encryption to get
ready. */
if ((rc = write_buffer (encsink, NULL, 0)))
goto failure;
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
encsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Write the final boundary (for OpenPGP) and finish the attachment. */
if (*boundary && (rc = write_boundary (sink, boundary, 1)))
goto failure;
if (close_mapi_attachment (&attach, sink))
goto failure;
if (finalize_message (message, att_table, protocol, 1))
goto failure;
result = 0; /* Everything is fine, fall through the cleanup now. */
failure:
if (result)
log_debug ("%s:%s: failed rc=%d (%s) <%s>", SRCNAME, __func__, rc,
gpg_strerror (rc), gpg_strsource (rc));
engine_cancel (filter);
gpgol_release (tmpstream);
mapi_release_attach_table (att_table);
xfree (my_sender);
return result;
}
int
restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
msgtype_t type, char *msgcls)
{
struct sink_s sinkmem;
sink_t sink = &sinkmem;
char *orig = NULL;
int err = -1;
char boundary[BOUNDARYSIZE+1];
(void)msgcls;
LPATTACH new_attach = create_mapi_attachment (message,
sink);
log_debug ("Restore message from moss called.");
if (!new_attach)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
// TODO MORE
if (type == MSGTYPE_SMIME)
{
create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
}
else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
{
create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
}
else
{
log_error ("%s:%s: Unsupported messagetype: %i",
SRCNAME, __func__, type);
goto done;
}
orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
if (!orig)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (write_string (sink, orig))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (*boundary && write_boundary (sink, boundary, 1))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (close_mapi_attachment (&new_attach, sink))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
/* Set a special property so that we are later able to identify
messages signed or encrypted by us. */
if (mapi_set_sig_status (message, "@"))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
err = 0;
done:
xfree (orig);
return err;
}
diff --git a/src/mimemaker.h b/src/mimemaker.h
index 101f750..c89a495 100644
--- a/src/mimemaker.h
+++ b/src/mimemaker.h
@@ -1,77 +1,79 @@
/* mimemaker.h - Construct MIME from MAPI
* Copyright (C) 2007, 2008 g10 Code GmbH
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MIMEMAKER_H
#define MIMEMAKER_H
class Mail;
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
/* The object we use instead of IStream. It allows us to have a
callback method for output and thus for processing stuff
recursively. */
struct sink_s;
typedef struct sink_s *sink_t;
struct sink_s
{
void *cb_data;
sink_t extrasink;
int (*writefnc)(sink_t sink, const void *data, size_t datalen);
unsigned long enc_counter; /* Used by write_buffer_for_cb. */
/* struct { */
/* int idx; */
/* unsigned char inbuf[4]; */
/* int quads; */
/* } b64; */
};
int mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
const char *sender, Mail* mail);
int mime_encrypt (LPMESSAGE message, HWND hwnd,
protocol_t protocol, char **recipients,
const char *sender, Mail* mail);
int mime_sign_encrypt (LPMESSAGE message, HWND hwnd,
protocol_t protocol, char **recipients,
const char *sender, Mail* mail);
int sink_std_write (sink_t sink, const void *data, size_t datalen);
int sink_file_write (sink_t sink, const void *data, size_t datalen);
int sink_encryption_write (sink_t encsink, const void *data, size_t datalen);
int write_buffer_for_cb (void *opaque, const void *data, size_t datalen);
int write_buffer (sink_t sink, const void *data, size_t datalen);
/** @brief Try to restore a message from the moss attachment.
*
* Try to turn the moss attachment back into a Mail that other
* MUAs could handle. Uses all the tricks available to archive
* that. Returns 0 on success.
*/
int restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
msgtype_t type, char *msgcls);
+int count_usable_attachments (mapi_attach_item_t *table);
+
#ifdef __cplusplus
}
#endif
#endif /*MIMEMAKER_H*/

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 17, 1:14 AM (1 d, 19 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
6b/1e/ff6f8a6f145de8604f1fb01cc2ce

Event Timeline