diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp
index 5aa1022..bc308cb 100644
--- a/src/cryptcontroller.cpp
+++ b/src/cryptcontroller.cpp
@@ -1,1028 +1,1049 @@
/* @file cryptcontroller.cpp
* @brief Helper to do crypto on a mail.
*
* Copyright (C) 2018 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 .
*/
#include "config.h"
#include "common.h"
#include "cpphelp.h"
#include "cryptcontroller.h"
#include "mail.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "wks-helper.h"
#include "overlay.h"
#include "keycache.h"
#include
#include
#include
#include "common.h"
#include
#define DEBUG_RESOLVER 1
static int
sink_data_write (sink_t sink, const void *data, size_t datalen)
{
GpgME::Data *d = static_cast(sink->cb_data);
d->write (data, datalen);
return 0;
}
static int
create_sign_attach (sink_t sink, protocol_t protocol,
GpgME::Data &signature,
GpgME::Data &signedData,
const char *micalg);
/** We have some C Style cruft in here as this was historically how
GpgOL worked directly in the MAPI data objects. To reduce the regression
risk the new object oriented way for crypto reused as much as possible
from this.
*/
CryptController::CryptController (Mail *mail, bool encrypt, bool sign,
GpgME::Protocol proto):
m_mail (mail),
m_encrypt (encrypt),
m_sign (sign),
m_crypto_success (false),
m_proto (proto)
{
log_debug ("%s:%s: CryptController ctor for %p encrypt %i sign %i inline %i.",
SRCNAME, __func__, mail, encrypt, sign, mail->do_pgp_inline ());
m_recipient_addrs = mail->take_cached_recipients ();
}
CryptController::~CryptController()
{
log_debug ("%s:%s:%p",
SRCNAME, __func__, m_mail);
release_cArray (m_recipient_addrs);
}
int
CryptController::collect_data ()
{
/* Get the attachment info and the body. We need to do this before
creating the engine's filter because sending the cancel to
the engine with nothing for the engine to process. Will result
in an error. 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. */
/* Take the Body from the mail if possible. This is a fix for
GnuPG-Bug-ID: T3614 because the body is not always properly
updated in MAPI when sending. */
char *body = m_mail->take_cached_plain_body ();
if (body && !*body)
{
xfree (body);
body = nullptr;
}
LPMESSAGE message = get_oom_base_message (m_mail->item ());
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
}
auto att_table = mapi_create_attach_table (message, 0);
int n_att_usable = count_usable_attachments (att_table);
if (!n_att_usable && !body)
{
gpgol_message_box (m_mail->get_window(),
utf8_gettext ("Can't encrypt / sign an empty message."),
utf8_gettext ("GpgOL"), MB_OK);
gpgol_release (message);
xfree (body);
return -1;
}
bool do_inline = m_mail->do_pgp_inline ();
if (n_att_usable && do_inline)
{
log_debug ("%s:%s: PGP Inline not supported for attachments."
" Using PGP MIME",
SRCNAME, __func__);
do_inline = false;
m_mail->set_do_pgp_inline (false);
}
else if (do_inline)
{
/* Inline. Use Body as input.
We need to collect also our mime structure for S/MIME
as we don't know yet if we are S/MIME or OpenPGP */
m_bodyInput.write (body, strlen (body));
log_debug ("%s:%s: Inline. Caching body.",
SRCNAME, __func__);
/* Set the input buffer to start. */
m_bodyInput.seek (0, SEEK_SET);
}
/* Set up the sink object to collect the mime structure */
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &m_input;
sink->writefnc = sink_data_write;
/* Collect the mime strucutre */
if (add_body_and_attachments (sink, message, att_table, m_mail,
body, n_att_usable))
{
log_error ("%s:%s: Collecting body and attachments failed.",
SRCNAME, __func__);
gpgol_release (message);
return -1;
}
/* Message is no longer needed */
gpgol_release (message);
/* Set the input buffer to start. */
m_input.seek (0, SEEK_SET);
return 0;
}
int
CryptController::lookup_fingerprints (const std::string &sigFpr,
const std::vector recpFprs)
{
auto ctx = std::shared_ptr (GpgME::Context::createForProtocol (m_proto));
if (!ctx)
{
log_error ("%s:%s: failed to create context with protocol '%s'",
SRCNAME, __func__,
m_proto == GpgME::CMS ? "smime" :
m_proto == GpgME::OpenPGP ? "openpgp" :
"unknown");
return -1;
}
ctx->setKeyListMode (GpgME::Local);
GpgME::Error err;
if (!sigFpr.empty()) {
m_signer_key = ctx->key (sigFpr.c_str (), err, true);
if (err || m_signer_key.isNull ()) {
log_error ("%s:%s: failed to lookup key for '%s' with protocol '%s'",
SRCNAME, __func__, sigFpr.c_str (),
m_proto == GpgME::CMS ? "smime" :
m_proto == GpgME::OpenPGP ? "openpgp" :
"unknown");
return -1;
}
// reset context
ctx = std::shared_ptr (GpgME::Context::createForProtocol (m_proto));
ctx->setKeyListMode (GpgME::Local);
}
if (!recpFprs.size()) {
return 0;
}
// Convert recipient fingerprints
char **cRecps = vector_to_cArray (recpFprs);
err = ctx->startKeyListing (const_cast (cRecps));
if (err) {
log_error ("%s:%s: failed to start recipient keylisting",
SRCNAME, __func__);
return -1;
}
do {
m_recipients.push_back(ctx->nextKey(err));
} while (!err);
m_recipients.pop_back();
release_cArray (cRecps);
return 0;
}
int
CryptController::parse_output (GpgME::Data &resolverOutput)
{
// Todo: Use Data::toString
std::istringstream ss(resolverOutput.toString());
std::string line;
std::string sigFpr;
std::vector recpFprs;
while (std::getline (ss, line))
{
rtrim (line);
if (line == "cancel")
{
log_debug ("%s:%s: resolver canceled",
SRCNAME, __func__);
return -2;
}
if (line == "unencrypted")
{
log_debug ("%s:%s: FIXME resolver wants unencrypted",
SRCNAME, __func__);
return -1;
}
std::istringstream lss (line);
// First is sig or enc
std::string what;
std::string how;
std::string fingerprint;
std::getline (lss, what, ':');
std::getline (lss, how, ':');
std::getline (lss, fingerprint, ':');
if (m_proto == GpgME::UnknownProtocol)
{
m_proto = (how == "smime") ? GpgME::CMS : GpgME::OpenPGP;
}
if (what == "sig")
{
if (!sigFpr.empty ())
{
log_error ("%s:%s: multiple signing keys not supported",
SRCNAME, __func__);
}
sigFpr = fingerprint;
continue;
}
if (what == "enc")
{
recpFprs.push_back (fingerprint);
}
}
if (m_sign && sigFpr.empty())
{
log_error ("%s:%s: Sign requested but no signing fingerprint - sending unsigned",
SRCNAME, __func__);
m_sign = false;
}
if (m_encrypt && !recpFprs.size())
{
log_error ("%s:%s: Encrypt requested but no recipient fingerprints",
SRCNAME, __func__);
return -1;
}
return lookup_fingerprints (sigFpr, recpFprs);
}
int
CryptController::resolve_keys_cached()
{
const auto cache = KeyCache::instance();
bool fallbackToSMIME = false;
if (m_encrypt)
{
const auto cached_sender = m_mail->get_cached_sender ();
auto recps = cArray_to_vector ((const char**) m_recipient_addrs);
recps.push_back (cached_sender);
m_recipients = cache->getEncryptionKeys(recps, GpgME::OpenPGP);
m_proto = GpgME::OpenPGP;
if (m_recipients.empty() && opt.enable_smime)
{
m_recipients = cache->getEncryptionKeys(recps, GpgME::CMS);
fallbackToSMIME = true;
m_proto = GpgME::CMS;
}
if (m_recipients.empty())
{
log_debug ("%s:%s: Failed to resolve keys through cache",
SRCNAME, __func__);
m_proto = GpgME::UnknownProtocol;
return 1;
}
}
if (m_sign)
{
if (!fallbackToSMIME)
{
m_signer_key = cache->getSigningKey (m_mail->get_cached_sender ().c_str (),
GpgME::OpenPGP);
m_proto = GpgME::OpenPGP;
}
if (m_signer_key.isNull() && opt.enable_smime)
{
m_signer_key = cache->getSigningKey (m_mail->get_cached_sender ().c_str (),
GpgME::CMS);
m_proto = GpgME::CMS;
}
if (m_signer_key.isNull())
{
log_debug ("%s:%s: Failed to resolve signer key through cache",
SRCNAME, __func__);
m_recipients.clear();
m_proto = GpgME::UnknownProtocol;
return 1;
}
}
return 0;
}
int
CryptController::resolve_keys ()
{
m_recipients.clear();
if (opt.autoresolve && !resolve_keys_cached ())
{
log_debug ("%s:%s: resolved keys through the cache",
SRCNAME, __func__);
start_crypto_overlay();
return 0;
}
std::vector args;
// Collect the arguments
char *gpg4win_dir = get_gpg4win_dir ();
if (!gpg4win_dir)
{
TRACEPOINT;
return -1;
}
const auto resolver = std::string (gpg4win_dir) + "\\bin\\resolver.exe";
args.push_back (resolver);
log_debug ("%s:%s: resolving keys with '%s'",
SRCNAME, __func__, resolver.c_str ());
// We want debug output as OutputDebugString
args.push_back (std::string ("--debug"));
// Yes passing it as int is ok.
auto wnd = m_mail->get_window ();
if (wnd)
{
// Pass the handle of the active window for raise / overlay.
args.push_back (std::string ("--hwnd"));
args.push_back (std::to_string ((int) (intptr_t) wnd));
}
// Set the overlay caption
args.push_back (std::string ("--overlayText"));
if (m_encrypt)
{
args.push_back (std::string (_("Resolving recipients...")));
}
else if (m_sign)
{
args.push_back (std::string (_("Resolving signers...")));
}
if (!opt.enable_smime)
{
args.push_back (std::string ("--protocol"));
args.push_back (std::string ("pgp"));
}
if (m_sign)
{
args.push_back (std::string ("--sign"));
}
const auto cached_sender = m_mail->get_cached_sender ();
if (cached_sender.empty())
{
log_error ("%s:%s: resolve keys without sender.",
SRCNAME, __func__);
}
else
{
args.push_back (std::string ("--sender"));
args.push_back (cached_sender);
}
if (!opt.autoresolve)
{
args.push_back (std::string ("--alwaysShow"));
}
if (m_encrypt)
{
args.push_back (std::string ("--encrypt"));
// Get the recipients that are cached from OOM
for (size_t i = 0; m_recipient_addrs && m_recipient_addrs[i]; i++)
{
args.push_back (GpgME::UserID::addrSpecFromString (m_recipient_addrs[i]));
}
}
args.push_back (std::string ("--lang"));
args.push_back (std::string (gettext_localename ()));
// Args are prepared. Spawn the resolver.
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
// can't happen
TRACEPOINT;
return -1;
}
// Convert our collected vector to c strings
// It's a bit overhead but should be quick for such small
// data.
char **cargs = vector_to_cArray (args);
#ifdef DEBUG_RESOLVER
log_debug ("Spawning args:");
for (size_t i = 0; cargs && cargs[i]; i++)
{
log_debug (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
}
#endif
GpgME::Data mystdin (GpgME::Data::null), mystdout, mystderr;
GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs),
mystdin, mystdout, mystderr,
(GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
// Somehow Qt messes up which window to bring back to front.
// So we do it manually.
bring_to_front (wnd);
// We need to create an overlay while encrypting as pinentry can take a while
start_crypto_overlay();
#ifdef DEBUG_RESOLVER
log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
#endif
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: Resolver spawn finished Err code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
}
int ret = parse_output (mystdout);
if (ret == -1)
{
log_debug ("%s:%s: Failed to parse / resolve keys.",
SRCNAME, __func__);
log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
return -1;
}
return ret;
}
int
CryptController::do_crypto ()
{
log_debug ("%s:%s",
SRCNAME, __func__);
/* Start a WKS check if necessary. */
WKSHelper::instance()->start_check (m_mail->get_cached_sender ());
int ret = resolve_keys ();
if (ret == -1)
{
//error
log_debug ("%s:%s: Failure to resolve keys.",
SRCNAME, __func__);
return -1;
}
if (ret == -2)
{
// Cancel
return -2;
}
bool do_inline = m_mail->do_pgp_inline ();
if (m_proto == GpgME::CMS && do_inline)
{
log_debug ("%s:%s: Inline for S/MIME not supported. Switching to mime.",
SRCNAME, __func__);
do_inline = false;
m_mail->set_do_pgp_inline (false);
m_bodyInput = GpgME::Data(GpgME::Data::null);
}
auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(m_proto));
if (!ctx)
{
log_error ("%s:%s: Failure to create context.",
SRCNAME, __func__);
gpgol_message_box (m_mail->get_window (),
"Failure to create context.",
utf8_gettext ("GpgOL"), MB_OK);
return -1;
}
if (!m_signer_key.isNull())
{
ctx->addSigningKey (m_signer_key);
}
ctx->setTextMode (m_proto == GpgME::OpenPGP);
ctx->setArmor (m_proto == GpgME::OpenPGP);
if (m_encrypt && m_sign && do_inline)
{
// Sign encrypt combined
const auto result_pair = ctx->signAndEncrypt (m_recipients,
do_inline ? m_bodyInput : m_input,
m_output,
GpgME::Context::AlwaysTrust);
if (result_pair.first.error() || result_pair.second.error())
{
log_error ("%s:%s: Encrypt / Sign error %s %s.",
SRCNAME, __func__, result_pair.first.error().asString(),
result_pair.second.error().asString());
return -1;
}
if (result_pair.first.error().isCanceled() || result_pair.second.error().isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
}
else if (m_encrypt && m_sign)
{
// First sign then encrypt
const auto sigResult = ctx->sign (m_input, m_output,
GpgME::Detached);
if (sigResult.error())
{
log_error ("%s:%s: Signing error %s.",
SRCNAME, __func__, sigResult.error().asString());
return -1;
}
if (sigResult.error().isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
parse_micalg (sigResult);
// We now have plaintext in m_input
// The detached signature in m_output
// Set up the sink object to construct the multipart/signed
GpgME::Data multipart;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &multipart;
sink->writefnc = sink_data_write;
if (create_sign_attach (sink,
m_proto == GpgME::CMS ?
PROTOCOL_SMIME : PROTOCOL_OPENPGP,
m_output, m_input, m_micalg.c_str ()))
{
TRACEPOINT;
return -1;
}
// Now we have the multipart throw away the rest.
m_output = GpgME::Data ();
m_input = GpgME::Data ();
multipart.seek (0, SEEK_SET);
const auto encResult = ctx->encrypt (m_recipients, multipart,
m_output,
GpgME::Context::AlwaysTrust);
if (encResult.error())
{
log_error ("%s:%s: Encryption error %s.",
SRCNAME, __func__, encResult.error().asString());
return -1;
}
if (encResult.error().isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
// Now we have encrypted output just treat it like encrypted.
}
else if (m_encrypt)
{
const auto result = ctx->encrypt (m_recipients, do_inline ? m_bodyInput : m_input,
m_output,
GpgME::Context::AlwaysTrust);
if (result.error())
{
log_error ("%s:%s: Encryption error %s.",
SRCNAME, __func__, result.error().asString());
return -1;
}
if (result.error().isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
}
else if (m_sign)
{
const auto result = ctx->sign (do_inline ? m_bodyInput : m_input, m_output,
do_inline ? GpgME::Clearsigned :
GpgME::Detached);
if (result.error())
{
log_error ("%s:%s: Signing error %s.",
SRCNAME, __func__, result.error().asString());
return -1;
}
if (result.error().isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
parse_micalg (result);
}
else
{
// ???
log_error ("%s:%s: unreachable code reached.",
SRCNAME, __func__);
}
log_debug ("%s:%s: Crypto done sucessfuly.",
SRCNAME, __func__);
m_crypto_success = true;
return 0;
}
static int
write_data (sink_t sink, GpgME::Data &data)
{
if (!sink || !sink->writefnc)
{
return -1;
}
char buf[4096];
size_t nread;
data.seek (0, SEEK_SET);
while ((nread = data.read (buf, 4096)) > 0)
{
sink->writefnc (sink, buf, nread);
}
return 0;
}
int
create_sign_attach (sink_t sink, protocol_t protocol,
GpgME::Data &signature,
GpgME::Data &signedData,
const char *micalg)
{
char boundary[BOUNDARYSIZE+1];
char top_header[BOUNDARYSIZE+200];
int rc = 0;
/* Write the top header. */
generate_boundary (boundary);
create_top_signing_header (top_header, sizeof top_header,
protocol, 1, boundary,
micalg);
if ((rc = write_string (sink, top_header)))
{
TRACEPOINT;
return rc;
}
/* Write the boundary so that it is not included in the hashing. */
if ((rc = write_boundary (sink, boundary, 0)))
{
TRACEPOINT;
return rc;
}
/* Write the signed mime structure */
if ((rc = write_data (sink, signedData)))
{
TRACEPOINT;
return rc;
}
/* Write the signature attachment */
if ((rc = write_boundary (sink, boundary, 0)))
{
TRACEPOINT;
return rc;
}
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"); */
}
if (rc)
{
TRACEPOINT;
return rc;
}
if ((rc = write_string (sink, "\r\n")))
{
TRACEPOINT;
return rc;
}
// Write the signature data
if (protocol == PROTOCOL_SMIME)
{
const std::string sigStr = signature.toString();
if ((rc = write_b64 (sink, (const void *) sigStr.c_str (), sigStr.size())))
{
TRACEPOINT;
return rc;
}
}
else if ((rc = write_data (sink, signature)))
{
TRACEPOINT;
return rc;
}
// Add an extra linefeed with should not harm.
if ((rc = write_string (sink, "\r\n")))
{
TRACEPOINT;
return rc;
}
/* Write the final boundary. */
if ((rc = write_boundary (sink, boundary, 1)))
{
TRACEPOINT;
return rc;
}
return rc;
}
static int
create_encrypt_attach (sink_t sink, protocol_t protocol,
- GpgME::Data &encryptedData)
+ GpgME::Data &encryptedData,
+ int exchange_major_version)
{
char boundary[BOUNDARYSIZE+1];
int rc = create_top_encryption_header (sink, protocol, boundary,
- false);
+ false, exchange_major_version);
// From here on use goto failure pattern.
if (rc)
{
log_error ("%s:%s: Failed to create top header.",
SRCNAME, __func__);
return rc;
}
- rc = write_data (sink, encryptedData);
+ if (protocol == PROTOCOL_OPENPGP ||
+ exchange_major_version >= 15)
+ {
+ // With exchange 2016 we have to construct S/MIME
+ // differently and write the raw data here.
+ rc = write_data (sink, encryptedData);
+ }
+ else
+ {
+ const auto encStr = encryptedData.toString();
+ rc = write_b64 (sink, encStr.c_str(), encStr.size());
+ }
if (rc)
{
log_error ("%s:%s: Failed to create top header.",
SRCNAME, __func__);
return rc;
}
/* Write the final boundary (for OpenPGP) and finish the attachment. */
if (*boundary && (rc = write_boundary (sink, boundary, 1)))
{
log_error ("%s:%s: Failed to write boundary.",
SRCNAME, __func__);
}
return rc;
}
int
CryptController::update_mail_mapi ()
{
log_debug ("%s:%s", SRCNAME, __func__);
if (m_mail->do_pgp_inline ())
{
// Nothing to do for inline.
log_debug ("%s:%s: Inline mail. No MAPI update.",
SRCNAME, __func__);
return 0;
}
LPMESSAGE message = get_oom_base_message (m_mail->item());
if (!message)
{
log_error ("%s:%s: Failed to obtain message.",
SRCNAME, __func__);
return -1;
}
mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0);
// Set up the sink object for our MSOXSMIME attachment.
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &m_input;
sink->writefnc = sink_data_write;
- // For S/MIME encrypted mails we have to use the multipart/encrypted
+ // For S/MIME encrypted mails we have to use the application/pkcs7-mime
// content type. Otherwise newer (2016) exchange servers will throw
// an M2MCVT.StorageError.Exeption (See GnuPG-Bug-Id: T3853 )
+
+ // This means that the conversion / build of the mime structure also
+ // happens differently.
+ int exchange_major_version = get_ex_major_version_for_addr (
+ m_mail->get_cached_sender ().c_str ());
+
std::string overrideMimeTag;
- if (m_proto == GpgME::CMS && m_encrypt)
+ if (m_proto == GpgME::CMS && m_encrypt && exchange_major_version >= 15)
{
+ log_debug ("%s:%s: CMS Encrypt with Exchange %i activating alternative.",
+ SRCNAME, __func__, exchange_major_version);
overrideMimeTag = "application/pkcs7-mime";
}
LPATTACH attach = create_mapi_attachment (message, sink,
overrideMimeTag.empty() ? nullptr :
overrideMimeTag.c_str());
if (!attach)
{
log_error ("%s:%s: Failed to create moss attach.",
SRCNAME, __func__);
gpgol_release (message);
return -1;
}
protocol_t protocol = m_proto == GpgME::CMS ?
PROTOCOL_SMIME :
PROTOCOL_OPENPGP;
+
int rc = 0;
/* Do we have override MIME ? */
const auto overrideMime = m_mail->get_override_mime_data ();
if (!overrideMime.empty())
{
rc = write_string (sink, overrideMime.c_str ());
}
else if (m_sign && m_encrypt)
{
- rc = create_encrypt_attach (sink, protocol, m_output);
+ rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version);
}
else if (m_encrypt)
{
- rc = create_encrypt_attach (sink, protocol, m_output);
+ rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version);
}
else if (m_sign)
{
rc = create_sign_attach (sink, protocol, m_output, m_input, m_micalg.c_str ());
}
// Close our attachment
if (!rc)
{
rc = close_mapi_attachment (&attach, sink);
}
// Set message class etc.
if (!rc)
{
rc = finalize_message (message, att_table, protocol, m_encrypt ? 1 : 0,
false);
}
// only on error.
if (rc)
{
cancel_mapi_attachment (&attach, sink);
}
// cleanup
mapi_release_attach_table (att_table);
gpgol_release (attach);
gpgol_release (message);
return rc;
}
std::string
CryptController::get_inline_data ()
{
std::string ret;
if (!m_mail->do_pgp_inline ())
{
return ret;
}
m_output.seek (0, SEEK_SET);
char buf[4096];
size_t nread;
while ((nread = m_output.read (buf, 4096)) > 0)
{
ret += std::string (buf, nread);
}
return ret;
}
void
CryptController::parse_micalg (const GpgME::SigningResult &result)
{
if (result.isNull())
{
TRACEPOINT;
return;
}
const auto signature = result.createdSignature(0);
if (signature.isNull())
{
TRACEPOINT;
return;
}
const char *hashAlg = signature.hashAlgorithmAsString ();
if (!hashAlg)
{
TRACEPOINT;
return;
}
if (m_proto == GpgME::OpenPGP)
{
m_micalg = std::string("pgp-") + hashAlg;
}
else
{
m_micalg = hashAlg;
}
std::transform(m_micalg.begin(), m_micalg.end(), m_micalg.begin(), ::tolower);
log_debug ("%s:%s: micalg is: '%s'.",
SRCNAME, __func__, m_micalg.c_str ());
}
void
CryptController::start_crypto_overlay ()
{
auto wid = m_mail->get_window ();
std::string text;
if (m_encrypt)
{
text = _("Encrypting...");
}
else if (m_sign)
{
text = _("Signing...");
}
m_overlay = std::unique_ptr (new Overlay (wid, text));
}
diff --git a/src/mimemaker.cpp b/src/mimemaker.cpp
index ed961f7..1e4a5d6 100644
--- a/src/mimemaker.cpp
+++ b/src/mimemaker.cpp
@@ -1,2445 +1,2450 @@
/* 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#define COBJMACROS
#include
#include
#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(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;
}
int
sink_string_write (sink_t sink, const void *data, size_t datalen)
{
Mail *mail = static_cast(sink->cb_data);
mail->append_to_inline_body (std::string((char*)data, datalen));
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. */
LPATTACH
create_mapi_attachment (LPMESSAGE message, sink_t sink,
const char *overrideMimeTag)
{
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 = overrideMimeTag ? strdup (overrideMimeTag) :
strdup ("multipart/signed");
if (overrideMimeTag)
{
log_debug ("%s:%s: using override mimetag: %s\n",
SRCNAME, __func__, overrideMimeTag);
}
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. */
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. */
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. */
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. */
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. */
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. */
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. */
int
finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
protocol_t protocol, int encrypt, bool is_inline)
{
HRESULT hr = 0;
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");
}
if (!is_inline)
{
/* For inline we stick with IPM.Note because Exchange Online would
error out if we tried our S/MIME conversion trick with a text
plain message */
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. */
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 Unsupported (but shows the data) strange.
We just cache it. Memory is cheap :-) */
char *html_body = mail->take_cached_html_body();
if (!html_body)
{
log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
SRCNAME, __func__);
return -1;
}
rc = write_part (sink, html_body, strlen (html_body),
alt_boundary, NULL, 2);
xfree (html_body);
if (rc)
{
TRACEPOINT;
return rc;
}
/* Finish our multipart */
return write_boundary (sink, alt_boundary, 1);
}
/* Add the body and attachments. Does multipart handling. */
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",
"\r\n", /* <--- Outlook adds an extra line. */
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",
"\r\n", /* <--- Outlook adds an extra line. */
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",
"\r\n", /* <--- Outlook adds an extra line. */
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;
}
/* Take the Body from the mail if possible. This is a fix for
GnuPG-Bug-ID: T3614 because the body is not always properly
updated in MAPI when sending. */
if (mail)
{
body = mail->take_cached_plain_body ();
}
/* Get the attachment info and the body. */
if (!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. */
int
create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
- bool is_inline)
+ bool is_inline, int exchange_major_version)
{
int rc;
if (is_inline)
{
*boundary = 0;
rc = 0;
/* This would be nice and worked for Google Sync but it failed
for Microsoft Exchange Online *sigh* so we put the body
instead into the oom body property and stick with IPM Note.
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 = 0;
- /*
- For S/MIME encrypted mails we do not use the S/MIME conversion
- code anymore. With Exchange 2016 this no longer works. Instead
- we set an override mime tag, the extended headers in OOM in
- Mail::update_crypt_oom and let outlook convert the attachment
- to base64.
-
- A bit more details can be found in T3853 / T3884
-
- rc = write_multistring (sink,
- "Content-Type: application/pkcs7-mime; "
- "smime-type=enveloped-data;\r\n"
- "\tname=\"smime.p7m\"\r\n"
- "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
- "Content-Transfer-Encoding: base64\r\n"
- "MIME-Version: 1.0\r\n"
- "\r\n",
- NULL);
- */
+ if (exchange_major_version >= 15)
+ {
+ /*
+ For S/MIME encrypted mails we do not use the S/MIME conversion
+ code anymore. With Exchange 2016 this no longer works. Instead
+ we set an override mime tag, the extended headers in OOM in
+ Mail::update_crypt_oom and let outlook convert the attachment
+ to base64.
+
+ A bit more details can be found in T3853 / T3884
+ */
+ rc = 0;
+ }
+ else
+ {
+ rc = write_multistring (sink,
+ "Content-Type: application/pkcs7-mime; "
+ "smime-type=enveloped-data;\r\n"
+ "\tname=\"smime.p7m\"\r\n"
+ "Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
+ "Content-Transfer-Encoding: base64\r\n"
+ "MIME-Version: 1.0\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 = nullptr;
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->do_pgp_inline ();
memset (sink, 0, sizeof *sink);
memset (encsink, 0, sizeof *encsink);
/* Get the attachment info and the body. We need to do this before
creating the engine's filter because sending the cancel to
the engine with nothing for the engine to process. Will result
in an error. 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. */
/* Take the Body from the mail if possible. This is a fix for
GnuPG-Bug-ID: T3614 because the body is not always properly
updated in MAPI when sending. */
if (mail)
{
body = mail->take_cached_plain_body ();
}
if (!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 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;
}
if (!is_inline || !mail)
{
attach = create_mapi_attachment (message, sink);
if (!attach)
return -1;
}
else
{
sink->cb_data = mail;
sink->writefnc = sink_string_write;
}
/* 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 (attach && 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);
if (attach)
{
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 4758bfd..eed47a3 100644
--- a/src/mimemaker.h
+++ b/src/mimemaker.h
@@ -1,100 +1,100 @@
/* 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 .
*/
#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);
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 create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
- bool is_inline = false);
+ bool is_inline = false, int exchange_major_version = -1);
/* Helper to write a boundary to the output sink. The leading LF
will be written as well. */
int write_boundary (sink_t sink, const char *boundary, int lastone);
LPATTACH create_mapi_attachment (LPMESSAGE message, sink_t sink,
const char *overrideMimeTag = nullptr);
int close_mapi_attachment (LPATTACH *attach, sink_t sink);
int finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
protocol_t protocol, int encrypt, bool is_inline = false);
void cancel_mapi_attachment (LPATTACH *attach, sink_t sink);
void create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
int first, const char *boundary, const char *micalg);
int write_string (sink_t sink, const char *text);
int write_b64 (sink_t sink, const void *data, size_t datalen);
#ifdef __cplusplus
}
#endif
#endif /*MIMEMAKER_H*/
diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp
index 51d025b..4ba8715 100644
--- a/src/oomhelp.cpp
+++ b/src/oomhelp.cpp
@@ -1,2036 +1,2059 @@
/* oomhelp.cpp - Helper functions for the Outlook Object Model
* Copyright (C) 2009 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "myexchext.h"
#include "common.h"
#include "oomhelp.h"
#include "gpgoladdin.h"
/* Return a malloced string with the utf-8 encoded name of the object
or NULL if not available. */
char *
get_object_name (LPUNKNOWN obj)
{
HRESULT hr;
LPDISPATCH disp = NULL;
LPTYPEINFO tinfo = NULL;
BSTR bstrname;
char *name = NULL;
if (!obj)
goto leave;
obj->QueryInterface (IID_IDispatch, (void **)&disp);
if (!disp)
goto leave;
disp->GetTypeInfo (0, 0, &tinfo);
if (!tinfo)
{
log_debug ("%s:%s: no typeinfo found for object\n",
SRCNAME, __func__);
goto leave;
}
bstrname = NULL;
hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0);
if (hr || !bstrname)
log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n",
SRCNAME, __func__, hr);
if (bstrname)
{
name = wchar_to_utf8 (bstrname);
SysFreeString (bstrname);
}
leave:
if (tinfo)
gpgol_release (tinfo);
if (disp)
gpgol_release (disp);
return name;
}
/* Lookup the dispid of object PDISP for member NAME. Returns
DISPID_UNKNOWN on error. */
DISPID
lookup_oom_dispid (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
DISPID dispid;
wchar_t *wname;
if (!pDisp || !name)
return DISPID_UNKNOWN; /* Error: Invalid arg. */
wname = utf8_to_wchar (name);
if (!wname)
return DISPID_UNKNOWN;/* Error: Out of memory. */
hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1,
LOCALE_SYSTEM_DEFAULT, &dispid);
xfree (wname);
if (hr != S_OK || dispid == DISPID_UNKNOWN)
log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n",
SRCNAME, __func__, name, (int)dispid, (unsigned int)hr);
if (hr != S_OK)
dispid = DISPID_UNKNOWN;
return dispid;
}
static void
init_excepinfo (EXCEPINFO *err)
{
if (!err)
{
return;
}
err->wCode = 0;
err->wReserved = 0;
err->bstrSource = nullptr;
err->bstrDescription = nullptr;
err->bstrHelpFile = nullptr;
err->dwHelpContext = 0;
err->pvReserved = nullptr;
err->pfnDeferredFillIn = nullptr;
err->scode = 0;
}
void
dump_excepinfo (EXCEPINFO err)
{
log_debug ("%s:%s: Exception: \n"
" wCode: 0x%x\n"
" wReserved: 0x%x\n"
" source: %S\n"
" desc: %S\n"
" help: %S\n"
" helpCtx: 0x%x\n"
" deferredFill: %p\n"
" scode: 0x%x\n",
SRCNAME, __func__, (unsigned int) err.wCode,
(unsigned int) err.wReserved,
err.bstrSource ? err.bstrSource : L"null",
err.bstrDescription ? err.bstrDescription : L"null",
err.bstrHelpFile ? err.bstrDescription : L"null",
(unsigned int) err.dwHelpContext,
err.pfnDeferredFillIn,
(unsigned int) err.scode);
}
/* Return the OOM object's IDispatch interface described by FULLNAME.
Returns NULL if not found. PSTART is the object where the search
starts. FULLNAME is a dot delimited sequence of object names. If
an object name has a "(foo)" suffix this passes it as a parameter
to the invoke function (i.e. using (DISPATCH|PROPERTYGET)). Object
names including the optional suffix are truncated at 127 byte. */
LPDISPATCH
get_oom_object (LPDISPATCH pStart, const char *fullname)
{
HRESULT hr;
LPDISPATCH pObj = pStart;
LPDISPATCH pDisp = NULL;
log_oom ("%s:%s: looking for %p->`%s'",
SRCNAME, __func__, pStart, fullname);
while (pObj)
{
DISPPARAMS dispparams;
VARIANT aVariant[4];
VARIANT vtResult;
wchar_t *wname;
char name[128];
int n_parms = 0;
BSTR parmstr = NULL;
INT parmint = 0;
DISPID dispid;
char *p, *pend;
int dispmethod;
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
if (pDisp)
{
gpgol_release (pDisp);
pDisp = NULL;
}
if (pObj->QueryInterface (IID_IDispatch, (LPVOID*)&pDisp) != S_OK)
{
log_error ("%s:%s Object does not support IDispatch",
SRCNAME, __func__);
gpgol_release (pObj);
return NULL;
}
/* Confirmed through testing that the retval needs a release */
if (pObj != pStart)
gpgol_release (pObj);
pObj = NULL;
if (!pDisp)
return NULL; /* The object has no IDispatch interface. */
if (!*fullname)
{
log_oom ("%s:%s: got %p",SRCNAME, __func__, pDisp);
return pDisp; /* Ready. */
}
/* Break out the next name part. */
{
const char *dot;
size_t n;
dot = strchr (fullname, '.');
if (dot == fullname)
{
gpgol_release (pDisp);
return NULL; /* Empty name part: error. */
}
else if (dot)
n = dot - fullname;
else
n = strlen (fullname);
if (n >= sizeof name)
n = sizeof name - 1;
strncpy (name, fullname, n);
name[n] = 0;
if (dot)
fullname = dot + 1;
else
fullname += strlen (fullname);
}
if (!strncmp (name, "get_", 4) && name[4])
{
dispmethod = DISPATCH_PROPERTYGET;
memmove (name, name+4, strlen (name+4)+1);
}
else if ((p = strchr (name, '(')))
{
*p++ = 0;
pend = strchr (p, ')');
if (pend)
*pend = 0;
if (*p == ',' && p[1] != ',')
{
/* We assume this is "foo(,30007)". I.e. the frst arg
is not given and the second one is an integer. */
parmint = (int)strtol (p+1, NULL, 10);
n_parms = 4;
}
else
{
wname = utf8_to_wchar (p);
if (wname)
{
parmstr = SysAllocString (wname);
xfree (wname);
}
if (!parmstr)
{
gpgol_release (pDisp);
return NULL; /* Error: Out of memory. */
}
n_parms = 1;
}
dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET;
}
else
dispmethod = DISPATCH_METHOD;
/* Lookup the dispid. */
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
{
if (parmstr)
SysFreeString (parmstr);
gpgol_release (pDisp);
return NULL; /* Name not found. */
}
/* Invoke the method. */
dispparams.rgvarg = aVariant;
dispparams.cArgs = 0;
if (n_parms)
{
if (n_parms == 4)
{
dispparams.rgvarg[0].vt = VT_ERROR;
dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[1].vt = VT_ERROR;
dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[2].vt = VT_INT;
dispparams.rgvarg[2].intVal = parmint;
dispparams.rgvarg[3].vt = VT_ERROR;
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.cArgs = n_parms;
}
else if (n_parms == 1 && parmstr)
{
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = parmstr;
dispparams.cArgs++;
}
}
dispparams.cNamedArgs = 0;
VariantInit (&vtResult);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
dispmethod, &dispparams,
&vtResult, &execpinfo, &argErr);
if (parmstr)
SysFreeString (parmstr);
if (hr != S_OK || vtResult.vt != VT_DISPATCH)
{
log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x",
SRCNAME, __func__,
name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
VariantClear (&vtResult);
gpgol_release (pDisp);
return NULL; /* Invoke failed. */
}
pObj = vtResult.pdispVal;
}
log_debug ("%s:%s: no object", SRCNAME, __func__);
return NULL;
}
/* Helper for put_oom_icon. */
static int
put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask)
{
HRESULT hr;
PICTDESC pdesc;
LPDISPATCH pPict;
DISPID dispid_put = DISPID_PROPERTYPUT;
UINT fuload;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[2];
/* When loading the mask we need to set the monochrome flag. We
better create a DIB section to avoid possible rendering
problems. */
fuload = LR_CREATEDIBSECTION | LR_SHARED;
if (is_mask)
fuload |= LR_MONOCHROME;
memset (&pdesc, 0, sizeof pdesc);
pdesc.cbSizeofstruct = sizeof pdesc;
pdesc.picType = PICTYPE_BITMAP;
pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst,
MAKEINTRESOURCE (resource),
IMAGE_BITMAP, size, size, fuload);
if (!pdesc.bmp.hbitmap)
{
log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n",
SRCNAME, __func__, resource);
return -1;
}
/* Wrap the image into an OLE object. */
hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
TRUE, (void **) &pPict);
if (hr != S_OK || !pPict)
{
log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
/* Store to the Picture or Mask property of the CommandBarButton. */
dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_DISPATCH;
dispparams.rgvarg[0].pdispVal = pPict;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr);
return -1;
}
return 0;
}
/* Update the icon of PDISP using the bitmap with RESOURCE ID. The
function adds the system pixel size to the resource id to compute
the actual icon size. The resource id of the mask is the N+1. */
int
put_oom_icon (LPDISPATCH pDisp, int resource_id, int size)
{
int rc;
/* This code is only relevant for Outlook < 2010.
Ideally it should grab the system pixel size and use an
icon of the appropiate size (e.g. 32 or 64px)
*/
rc = put_picture_or_mask (pDisp, resource_id, size, 0);
if (!rc)
rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1);
return rc;
}
/* Set the boolean property NAME to VALUE. */
int
put_oom_bool (LPDISPATCH pDisp, const char *name, int value)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL;
dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
return -1;
}
return 0;
}
/* Set the property NAME to VALUE. */
int
put_oom_int (LPDISPATCH pDisp, const char *name, int value)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = value;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
return -1;
}
return 0;
}
/* Set the property NAME to STRING. */
int
put_oom_string (LPDISPATCH pDisp, const char *name, const char *string)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
BSTR bstring;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
{
wchar_t *tmp = utf8_to_wchar (string);
bstring = tmp? SysAllocString (tmp):NULL;
xfree (tmp);
if (!bstring)
{
log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
return -1;
}
}
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstring;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, &execpinfo, NULL);
SysFreeString (bstring);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
return 0;
}
/* Set the property NAME to DISP. */
int
put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_DISPATCH;
dispparams.rgvarg[0].pdispVal = disp;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUTREF, &dispparams,
NULL, &execpinfo, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
return 0;
}
/* Get the boolean property NAME of the object PDISP. Returns False if
not found or if it is not a boolean property. */
int
get_oom_bool (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
int result = 0;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_BOOL)
log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
result = !!rVariant.boolVal;
VariantClear (&rVariant);
}
return result;
}
/* Get the integer property NAME of the object PDISP. Returns 0 if
not found or if it is not an integer property. */
int
get_oom_int (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
int result = 0;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4)
log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
result = rVariant.intVal;
VariantClear (&rVariant);
}
return result;
}
/* Get the string property NAME of the object PDISP. Returns NULL if
not found or if it is not a string property. */
char *
get_oom_string (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
char *result = NULL;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_BSTR)
log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else if (rVariant.bstrVal)
result = wchar_to_utf8 (rVariant.bstrVal);
VariantClear (&rVariant);
}
return result;
}
/* Get the object property NAME of the object PDISP. Returns NULL if
not found or if it is not an object perty. */
LPUNKNOWN
get_oom_iunknown (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_UNKNOWN)
log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
return rVariant.punkVal;
VariantClear (&rVariant);
}
return NULL;
}
/* Return the control object described by the tag property with value
TAG. The object POBJ must support the FindControl method. Returns
NULL if not found. */
LPDISPATCH
get_oom_control_bytag (LPDISPATCH pDisp, const char *tag)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[4];
VARIANT rVariant;
BSTR bstring;
LPDISPATCH result = NULL;
dispid = lookup_oom_dispid (pDisp, "FindControl");
if (dispid == DISPID_UNKNOWN)
{
log_debug ("%s:%s: Object %p has no FindControl method",
SRCNAME, __func__, pDisp);
return NULL;
}
{
wchar_t *tmp = utf8_to_wchar (tag);
bstring = tmp? SysAllocString (tmp):NULL;
xfree (tmp);
if (!bstring)
{
log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
return NULL;
}
}
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */
dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[1].vt = VT_BSTR; /* Tag */
dispparams.rgvarg[1].bstrVal = bstring;
dispparams.rgvarg[2].vt = VT_ERROR; /* Id */
dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[3].vt = VT_ERROR;/* Type */
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.cArgs = 4;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, NULL, NULL);
SysFreeString (bstring);
if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal)
{
rVariant.pdispVal->QueryInterface (IID_IDispatch, (LPVOID*)&result);
gpgol_release (rVariant.pdispVal);
if (!result)
log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.",
SRCNAME, __func__, tag);
}
else
{
log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx",
SRCNAME, __func__, tag, rVariant.vt, hr);
VariantClear (&rVariant);
}
return result;
}
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
LPDISPATCH
add_oom_button (LPDISPATCH pObj)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[5];
VARIANT rVariant;
dispid = lookup_oom_dispid (pObj, "Add");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */
dispparams.rgvarg[0].boolVal = VARIANT_TRUE;
dispparams.rgvarg[1].vt = VT_ERROR; /* Before */
dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[2].vt = VT_ERROR; /* Parameter */
dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[3].vt = VT_ERROR; /* Id */
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[4].vt = VT_INT; /* Type */
dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON;
dispparams.cArgs = 5;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal)
{
log_error ("%s:%s: Adding Control failed: %#lx - vt=%d",
SRCNAME, __func__, hr, rVariant.vt);
VariantClear (&rVariant);
return NULL;
}
return rVariant.pdispVal;
}
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
void
del_oom_button (LPDISPATCH pObj)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[5];
dispid = lookup_oom_dispid (pObj, "Delete");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */
dispparams.rgvarg[0].boolVal = VARIANT_FALSE;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
log_error ("%s:%s: Deleting Control failed: %#lx",
SRCNAME, __func__, hr);
}
/* Gets the current contexts HWND. Returns NULL on error */
HWND
get_oom_context_window (LPDISPATCH context)
{
LPOLEWINDOW actExplorer;
HWND ret = NULL;
actExplorer = (LPOLEWINDOW) get_oom_object(context,
"Application.ActiveExplorer");
if (actExplorer)
actExplorer->GetWindow (&ret);
else
{
log_debug ("%s:%s: Could not find active window",
SRCNAME, __func__);
}
gpgol_release (actExplorer);
return ret;
}
int
put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value)
{
LPDISPATCH propertyAccessor;
VARIANT cVariant[2];
VARIANT rVariant;
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_property;
wchar_t *w_property;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
log_oom ("%s:%s: Looking up property: %s;",
SRCNAME, __func__, dasl_id);
propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
if (!propertyAccessor)
{
log_error ("%s:%s: Failed to look up property accessor.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "SetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find SetProperty DISPID",
SRCNAME, __func__);
return -1;
}
/* Prepare the parameter */
w_property = utf8_to_wchar (dasl_id);
b_property = SysAllocString (w_property);
xfree (w_property);
/* Variant 0 carries the data. */
VariantInit (&cVariant[0]);
if (VariantCopy (&cVariant[0], value))
{
log_error ("%s:%s: Falied to copy value.",
SRCNAME, __func__);
return -1;
}
/* Variant 1 is the DASL as found out by experiments. */
VariantInit (&cVariant[1]);
cVariant[1].vt = VT_BSTR;
cVariant[1].bstrVal = b_property;
dispparams.rgvarg = cVariant;
dispparams.cArgs = 2;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, &execpinfo, &argErr);
VariantClear (&cVariant[0]);
VariantClear (&cVariant[1]);
gpgol_release (propertyAccessor);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking SetProperty p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
(unsigned int)argErr);
VariantClear (&rVariant);
dump_excepinfo (execpinfo);
return -1;
}
VariantClear (&rVariant);
return 0;
}
int
put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value)
{
wchar_t *w_value = utf8_to_wchar (value);
BSTR b_value = SysAllocString(w_value);
VARIANT var;
VariantInit (&var);
var.vt = VT_BSTR;
var.bstrVal = b_value;
int ret = put_pa_variant (pDisp, dasl_id, &var);
VariantClear (&var);
return ret;
}
int
put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value)
{
VARIANT var;
VariantInit (&var);
var.vt = VT_INT;
var.intVal = value;
int ret = put_pa_variant (pDisp, dasl_id, &var);
VariantClear (&var);
return ret;
}
/* Get a MAPI property through OOM using the PropertyAccessor
* interface and the DASL Uid. Returns -1 on error.
* Variant has to be cleared with VariantClear.
* rVariant must be a pointer to a Variant.
*/
int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant)
{
LPDISPATCH propertyAccessor;
VARIANT cVariant[1];
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_property;
wchar_t *w_property;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
log_oom ("%s:%s: Looking up property: %s;",
SRCNAME, __func__, dasl_id);
propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
if (!propertyAccessor)
{
log_error ("%s:%s: Failed to look up property accessor.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "GetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find GetProperty DISPID",
SRCNAME, __func__);
return -1;
}
/* Prepare the parameter */
w_property = utf8_to_wchar (dasl_id);
b_property = SysAllocString (w_property);
xfree (w_property);
cVariant[0].vt = VT_BSTR;
cVariant[0].bstrVal = b_property;
dispparams.rgvarg = cVariant;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
VariantInit (rVariant);
hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
rVariant, &execpinfo, &argErr);
SysFreeString (b_property);
gpgol_release (propertyAccessor);
if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id))
{
/* It often happens that mails don't have a uid by us e.g. if
they are not crypto mails or just dont have one. This is
not an error. */
log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
rVariant->pdispVal, rVariant->vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
VariantClear (rVariant);
return -1;
}
return 0;
}
/* Get a property string by using the PropertyAccessor of pDisp
* returns NULL on error or a newly allocated result. */
char *
get_pa_string (LPDISPATCH pDisp, const char *property)
{
VARIANT rVariant;
char *result = NULL;
if (get_pa_variant (pDisp, property, &rVariant))
{
return NULL;
}
if (rVariant.vt == VT_BSTR && rVariant.bstrVal)
{
result = wchar_to_utf8 (rVariant.bstrVal);
}
else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF))
{
LONG uBound, lBound;
VARTYPE vt;
char *data;
SafeArrayGetVartype(rVariant.parray, &vt);
if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK ||
SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK ||
vt != VT_UI1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
VariantClear (&rVariant);
return NULL;
}
result = (char *)xmalloc (uBound - lBound + 1);
data = (char *) rVariant.parray->pvData;
memcpy (result, data + lBound, uBound - lBound);
result[uBound - lBound] = '\0';
}
else
{
log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
SRCNAME, __func__, property, rVariant.vt);
}
VariantClear (&rVariant);
return result;
}
int
get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt)
{
VARIANT rVariant;
if (get_pa_variant (pDisp, property, &rVariant))
{
return -1;
}
if (rVariant.vt != VT_I4)
{
log_debug ("%s:%s: Property `%s' is not a int (vt=%d)",
SRCNAME, __func__, property, rVariant.vt);
return -1;
}
*rInt = rVariant.lVal;
VariantClear (&rVariant);
return 0;
}
/* Helper for additional fallbacks in recipient lookup */
static char *
get_recipient_addr_fallbacks (LPDISPATCH recipient)
{
LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry");
if (!addr_entry)
{
log_debug ("%s:%s: Failed to find AddressEntry",
SRCNAME, __func__);
return nullptr;
}
/* Maybe check for type here? We are pretty sure that we are exchange */
/* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL
is more avilable then the SMTP Address. */
char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL);
if (ret)
{
log_debug ("%s:%s: Found recipient through AB_PROXY: %s",
SRCNAME, __func__, ret);
char *smtpbegin = strstr(ret, "SMTP:");
if (smtpbegin == ret)
{
ret += 5;
}
gpgol_release (addr_entry);
return ret;
}
else
{
log_debug ("%s:%s: Failed AB_PROXY lookup.",
SRCNAME, __func__);
}
LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser");
gpgol_release (addr_entry);
if (!ex_user)
{
log_debug ("%s:%s: Failed to find ExchangeUser",
SRCNAME, __func__);
return nullptr;
}
ret = get_oom_string (ex_user, "PrimarySmtpAddress");
gpgol_release (ex_user);
if (ret)
{
log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s",
SRCNAME, __func__, ret);
return ret;
}
return nullptr;
}
/* Gets a malloced NULL terminated array of recipent strings from
an OOM recipients Object. */
char **
get_oom_recipients (LPDISPATCH recipients)
{
int recipientsCnt = get_oom_int (recipients, "Count");
char **recipientAddrs = NULL;
int i;
if (!recipientsCnt)
{
return NULL;
}
/* Get the recipients */
recipientAddrs = (char**) xmalloc((recipientsCnt + 1) * sizeof(char*));
recipientAddrs[recipientsCnt] = NULL;
for (i = 1; i <= recipientsCnt; i++)
{
char buf[16];
LPDISPATCH recipient;
snprintf (buf, sizeof (buf), "Item(%i)", i);
recipient = get_oom_object (recipients, buf);
if (!recipient)
{
/* Should be impossible */
recipientAddrs[i-1] = NULL;
log_error ("%s:%s: could not find Item %i;",
SRCNAME, __func__, i);
break;
}
char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
if (resolved)
{
recipientAddrs[i-1] = resolved;
gpgol_release (recipient);
continue;
}
/* No PR_SMTP_ADDRESS first fallback */
resolved = get_recipient_addr_fallbacks (recipient);
gpgol_release (recipient);
if (resolved)
{
recipientAddrs[i-1] = resolved;
continue;
}
char *address = get_oom_string (recipient, "Address");
log_debug ("%s:%s: Failed to look up Address probably EX addr is returned",
SRCNAME, __func__);
recipientAddrs[i-1] = address;
}
return recipientAddrs;
}
/* Add an attachment to the outlook dispatcher disp
that has an Attachment property.
inFile is the path to the attachment. Name is the
name that should be used in outlook. */
int
add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW,
const wchar_t* displayName)
{
LPDISPATCH attachments = get_oom_object (disp, "Attachments");
DISPID dispid;
DISPPARAMS dispparams;
VARIANT vtResult;
VARIANT aVariant[4];
HRESULT hr;
BSTR inFileB = nullptr,
dispNameB = nullptr;
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (attachments, "Add");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find attachment dispatcher",
SRCNAME, __func__);
return -1;
}
if (inFileW)
{
inFileB = SysAllocString (inFileW);
}
if (displayName)
{
dispNameB = SysAllocString (displayName);
}
dispparams.rgvarg = aVariant;
/* Contrary to the documentation the Source is the last
parameter and not the first. Additionally DisplayName
is documented but gets ignored by Outlook since Outlook
2003 */
dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */
dispparams.rgvarg[0].bstrVal = dispNameB;
dispparams.rgvarg[1].vt = VT_INT; /* Position */
dispparams.rgvarg[1].intVal = 1;
dispparams.rgvarg[2].vt = VT_INT; /* Type */
dispparams.rgvarg[2].intVal = 1;
dispparams.rgvarg[3].vt = VT_BSTR; /* Source */
dispparams.rgvarg[3].bstrVal = inFileB;
dispparams.cArgs = 4;
dispparams.cNamedArgs = 0;
VariantInit (&vtResult);
hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&vtResult, &execpinfo, &argErr);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x",
SRCNAME, __func__,
vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
}
if (inFileB)
SysFreeString (inFileB);
if (dispNameB)
SysFreeString (dispNameB);
VariantClear (&vtResult);
gpgol_release (attachments);
return hr == S_OK ? 0 : -1;
}
LPDISPATCH
get_object_by_id (LPDISPATCH pDisp, REFIID id)
{
LPDISPATCH disp = NULL;
if (!pDisp)
return NULL;
if (pDisp->QueryInterface (id, (void **)&disp) != S_OK)
return NULL;
return disp;
}
LPDISPATCH
get_strong_reference (LPDISPATCH mail)
{
VARIANT var;
VariantInit (&var);
DISPPARAMS args;
VARIANT argvars[2];
VariantInit (&argvars[0]);
VariantInit (&argvars[1]);
argvars[1].vt = VT_DISPATCH;
argvars[1].pdispVal = mail;
argvars[0].vt = VT_INT;
argvars[0].intVal = 1;
args.cArgs = 2;
args.cNamedArgs = 0;
args.rgvarg = argvars;
LPDISPATCH ret = NULL;
if (!invoke_oom_method_with_parms (
GpgolAddin::get_instance()->get_application(),
"GetObjectReference", &var, &args))
{
ret = var.pdispVal;
log_oom ("%s:%s: Got strong ref %p for %p",
SRCNAME, __func__, ret, mail);
}
else
{
log_error ("%s:%s: Failed to get strong ref.",
SRCNAME, __func__);
}
VariantClear (&var);
return ret;
}
LPMESSAGE
get_oom_message (LPDISPATCH mailitem)
{
LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject");
if (!mapi_obj)
{
log_error ("%s:%s: Failed to obtain MAPI Message.",
SRCNAME, __func__);
return NULL;
}
return (LPMESSAGE) mapi_obj;
}
static LPMESSAGE
get_oom_base_message_from_mapi (LPDISPATCH mapi_message)
{
HRESULT hr;
LPDISPATCH secureItem = NULL;
LPMESSAGE message = NULL;
LPMAPISECUREMESSAGE secureMessage = NULL;
secureItem = get_object_by_id (mapi_message,
IID_IMAPISecureMessage);
if (!secureItem)
{
log_error ("%s:%s: Failed to obtain SecureItem.",
SRCNAME, __func__);
return NULL;
}
secureMessage = (LPMAPISECUREMESSAGE) secureItem;
/* The call to GetBaseMessage is pretty much a jump
in the dark. So it would not be surprising to get
crashes here in the future. */
log_oom_extra("%s:%s: About to call GetBaseMessage.",
SRCNAME, __func__);
hr = secureMessage->GetBaseMessage (&message);
gpgol_release (secureMessage);
if (hr != S_OK)
{
log_error_w32 (hr, "Failed to GetBaseMessage.");
return NULL;
}
return message;
}
LPMESSAGE
get_oom_base_message (LPDISPATCH mailitem)
{
LPMESSAGE mapi_message = get_oom_message (mailitem);
LPMESSAGE ret = NULL;
if (!mapi_message)
{
log_error ("%s:%s: Failed to obtain mapi_message.",
SRCNAME, __func__);
return NULL;
}
ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message);
gpgol_release (mapi_message);
return ret;
}
int
invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params)
{
HRESULT hr;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, params ? params : &dispparams,
rVariant, &execpinfo, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Method '%s' invokation failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
}
return 0;
}
int
invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant)
{
return invoke_oom_method_with_parms (pDisp, name, rVariant, NULL);
}
LPMAPISESSION
get_oom_mapi_session ()
{
LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
LPDISPATCH oom_session = NULL;
LPMAPISESSION session = NULL;
LPUNKNOWN mapiobj = NULL;
HRESULT hr;
if (!application)
{
log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
return NULL;
}
oom_session = get_oom_object (application, "Session");
if (!oom_session)
{
log_error ("%s:%s: session object not found", SRCNAME, __func__);
return NULL;
}
mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT");
gpgol_release (oom_session);
if (!mapiobj)
{
log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__);
return NULL;
}
session = NULL;
hr = mapiobj->QueryInterface (IID_IMAPISession, (void**)&session);
gpgol_release (mapiobj);
if (hr != S_OK || !session)
{
log_error ("%s:%s: error getting IMAPISession: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
return session;
}
static int
create_category (LPDISPATCH categories, const char *category, int color)
{
VARIANT cVariant[3];
VARIANT rVariant;
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_name;
wchar_t *w_name;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
if (!categories || !category)
{
TRACEPOINT;
return 1;
}
dispid = lookup_oom_dispid (categories, "Add");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find Add DISPID",
SRCNAME, __func__);
return -1;
}
/* Do the string dance */
w_name = utf8_to_wchar (category);
b_name = SysAllocString (w_name);
xfree (w_name);
/* Variants are in reverse order
ShortcutKey -> 0 / Int
Color -> 1 / Int
Name -> 2 / Bstr */
VariantInit (&cVariant[2]);
cVariant[2].vt = VT_BSTR;
cVariant[2].bstrVal = b_name;
VariantInit (&cVariant[1]);
cVariant[1].vt = VT_INT;
cVariant[1].intVal = color;
VariantInit (&cVariant[0]);
cVariant[0].vt = VT_INT;
cVariant[0].intVal = 0;
dispparams.cArgs = 3;
dispparams.cNamedArgs = 0;
dispparams.rgvarg = cVariant;
hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, &execpinfo, &argErr);
SysFreeString (b_name);
VariantClear (&cVariant[0]);
VariantClear (&cVariant[1]);
VariantClear (&cVariant[2]);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking Add p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
VariantClear (&rVariant);
return -1;
}
VariantClear (&rVariant);
log_debug ("%s:%s: Created category '%s'",
SRCNAME, __func__, category);
return 0;
}
void
ensure_category_exists (LPDISPATCH application, const char *category, int color)
{
if (!application || !category)
{
TRACEPOINT;
return;
}
log_debug ("Ensure category exists called for %s, %i", category, color);
LPDISPATCH stores = get_oom_object (application, "Session.Stores");
if (!stores)
{
log_error ("%s:%s: No stores found.",
SRCNAME, __func__);
return;
}
auto store_count = get_oom_int (stores, "Count");
for (int n = 1; n <= store_count; n++)
{
const auto store_str = std::string("Item(") + std::to_string(n) + ")";
LPDISPATCH store = get_oom_object (stores, store_str.c_str());
if (!store)
{
TRACEPOINT;
continue;
}
LPDISPATCH categories = get_oom_object (store, "Categories");
gpgol_release (store);
if (!categories)
{
categories = get_oom_object (application, "Session.Categories");
if (!categories)
{
TRACEPOINT;
continue;
}
}
auto count = get_oom_int (categories, "Count");
bool found = false;
for (int i = 1; i <= count && !found; i++)
{
const auto item_str = std::string("Item(") + std::to_string(i) + ")";
LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str());
if (!category_obj)
{
TRACEPOINT;
break;
}
char *name = get_oom_string (category_obj, "Name");
if (name && !strcmp (category, name))
{
log_debug ("%s:%s: Found category '%s'",
SRCNAME, __func__, name);
found = true;
}
/* We don't check the color here as the user may change that. */
gpgol_release (category_obj);
xfree (name);
}
if (!found)
{
if (create_category (categories, category, color))
{
log_debug ("%s:%s: Found category '%s'",
SRCNAME, __func__, category);
}
}
/* Otherwise we have to create the category */
gpgol_release (categories);
}
gpgol_release (stores);
}
int
add_category (LPDISPATCH mail, const char *category)
{
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
return 1;
}
if (strstr (tmp, category))
{
log_debug ("%s:%s: category '%s' already added.",
SRCNAME, __func__, category);
return 0;
}
std::string newstr (tmp);
xfree (tmp);
if (!newstr.empty ())
{
newstr += ", ";
}
newstr += category;
return put_oom_string (mail, "Categories", newstr.c_str ());
}
int
remove_category (LPDISPATCH mail, const char *category)
{
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
return 1;
}
std::string newstr (tmp);
xfree (tmp);
std::string cat (category);
size_t pos1 = newstr.find (cat);
size_t pos2 = newstr.find (std::string(", ") + cat);
if (pos1 == std::string::npos && pos2 == std::string::npos)
{
log_debug ("%s:%s: category '%s' not found.",
SRCNAME, __func__, category);
return 0;
}
size_t len = cat.size();
if (pos2)
{
len += 2;
}
newstr.erase (pos2 != std::string::npos ? pos2 : pos1, len);
log_debug ("%s:%s: removing category '%s'",
SRCNAME, __func__, category);
return put_oom_string (mail, "Categories", newstr.c_str ());
}
static char *
generate_uid ()
{
UUID uuid;
UuidCreate (&uuid);
unsigned char *str;
UuidToStringA (&uuid, &str);
char *ret = strdup ((char*)str);
RpcStringFreeA (&str);
return ret;
}
char *
get_unique_id (LPDISPATCH mail, int create, const char *uuid)
{
if (!mail)
{
return NULL;
}
/* Get the User Properties. */
if (!create)
{
char *uid = get_pa_string (mail, GPGOL_UID_DASL);
if (!uid)
{
log_debug ("%s:%s: No uuid found in oom for '%p'",
SRCNAME, __func__, mail);
return NULL;
}
else
{
log_debug ("%s:%s: Found uid '%s' for '%p'",
SRCNAME, __func__, uid, mail);
return uid;
}
}
char *newuid;
if (!uuid)
{
newuid = generate_uid ();
}
else
{
newuid = strdup (uuid);
}
int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid);
if (ret)
{
log_debug ("%s:%s: failed to set uid '%s' for '%p'",
SRCNAME, __func__, newuid, mail);
xfree (newuid);
return NULL;
}
log_debug ("%s:%s: '%p' has now the uid: '%s' ",
SRCNAME, __func__, mail, newuid);
return newuid;
}
HWND
get_active_hwnd ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow");
if (!activeWindow)
{
activeWindow = get_oom_object (app, "ActiveInspector");
if (!activeWindow)
{
activeWindow = get_oom_object (app, "ActiveExplorer");
if (!activeWindow)
{
TRACEPOINT;
return nullptr;
}
}
}
/* Both explorer and inspector have this. */
char *caption = get_oom_string (activeWindow, "Caption");
gpgol_release (activeWindow);
if (!caption)
{
TRACEPOINT;
return nullptr;
}
/* Might not be completly true for multiple explorers
on the same folder but good enugh. */
HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32",
caption);
xfree (caption);
return hwnd;
}
LPDISPATCH
create_mail ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
VARIANT var;
VariantInit (&var);
VARIANT argvars[1];
DISPPARAMS args;
VariantInit (&argvars[0]);
argvars[0].vt = VT_I2;
argvars[0].intVal = 0;
args.cArgs = 1;
args.cNamedArgs = 0;
args.rgvarg = argvars;
LPDISPATCH ret = nullptr;
if (invoke_oom_method_with_parms (app, "CreateItem", &var, &args))
{
log_error ("%s:%s: Failed to create mailitem.",
SRCNAME, __func__);
return ret;
}
ret = var.pdispVal;
return ret;
}
LPDISPATCH
get_account_for_mail (const char *mbox)
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH accounts = get_oom_object (app, "Session.Accounts");
if (!accounts)
{
TRACEPOINT;
return nullptr;
}
int count = get_oom_int (accounts, "Count");
for (int i = 1; i <= count; i++)
{
std::string item = std::string ("Item(") + std::to_string (i) + ")";
LPDISPATCH account = get_oom_object (accounts, item.c_str ());
if (!account)
{
TRACEPOINT;
continue;
}
char *smtpAddr = get_oom_string (account, "SmtpAddress");
if (!smtpAddr)
{
TRACEPOINT;
continue;
}
if (!stricmp (mbox, smtpAddr))
{
gpgol_release (accounts);
xfree (smtpAddr);
return account;
}
xfree (smtpAddr);
}
gpgol_release (accounts);
log_error ("%s:%s: Failed to find account for '%s'.",
SRCNAME, __func__, mbox);
return nullptr;
}
char *
get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite)
{
LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount");
if (!sender)
{
return nullptr;
}
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) && r_is_GSuite)
{
*r_is_GSuite = true;
}
xfree (dispName);
if (buf && strlen (buf))
{
log_debug ("%s:%s: found sender", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_Sender (LPDISPATCH mailitem)
{
LPDISPATCH sender = get_oom_object (mailitem, "Sender");
if (!sender)
{
return nullptr;
}
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__);
return buf;
}
xfree (buf);
/* We have a sender object but not yet an smtp address likely
exchange. Try some more propertys of the message. */
buf = get_pa_string (mailitem, PR_TAG_SENDER_SMTP_ADDRESS);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__);
return buf;
}
xfree (buf);
buf = get_pa_string (mailitem, PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 4", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_CurrentUser (LPDISPATCH mailitem)
{
LPDISPATCH sender = get_oom_object (mailitem,
"Session.CurrentUser");
if (!sender)
{
return nullptr;
}
char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
gpgol_release (sender);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 5", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_SenderEMailAddress (LPDISPATCH mailitem)
{
char *type = get_oom_string (mailitem, "SenderEmailType");
if (type && !strcmp ("SMTP", type))
{
char *senderMail = get_oom_string (mailitem, "SenderEmailAddress");
if (senderMail)
{
log_debug ("%s:%s: Sender found", SRCNAME, __func__);
xfree (type);
return senderMail;
}
}
xfree (type);
return nullptr;
}
char *
get_inline_body ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
return nullptr;
}
char *body = get_oom_string (inlineResponse, "Body");
gpgol_release (inlineResponse);
return body;
}
+
+int
+get_ex_major_version_for_addr (const char *mbox)
+{
+ LPDISPATCH account = get_account_for_mail (mbox);
+ if (!account)
+ {
+ TRACEPOINT;
+ return -1;
+ }
+
+ char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion");
+ gpgol_release (account);
+
+ if (!version_str)
+ {
+ return -1;
+ }
+ long int version = strtol (version_str, nullptr, 10);
+ xfree (version_str);
+
+ return (int) version;
+}
diff --git a/src/oomhelp.h b/src/oomhelp.h
index 3bf86a6..13faafb 100644
--- a/src/oomhelp.h
+++ b/src/oomhelp.h
@@ -1,350 +1,356 @@
/* oomhelp.h - Defs for helper functions for the Outlook Object Model
* Copyright (C) 2009 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 .
*/
#ifndef OOMHELP_H
#define OOMHELP_H
#include
#include "mymapi.h"
#include "myexchext.h"
#define MSOCONTROLBUTTON 1
#define MSOCONTROLEDIT 2
#define MSOCONTROLDROPDOWN 3
#define MSOCONTROLCOMBOBOX 4
#define MSOCONTROLPOPUP 10
enum
{
msoButtonAutomatic = 0,
msoButtonIcon = 1,
msoButtonCaption = 2,
msoButtonIconAndCaption = 3,
msoButtonIconAndWrapCaption = 7,
msoButtonIconAndCaptionBelow = 11,
msoButtonWrapCaption = 14,
msoButtonIconAndWrapCaptionBelow = 15
};
enum
{
msoButtonDown = -1,
msoButtonUp = 0,
msoButtonMixed = 2
};
DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
DEFINE_GUID(IID_IConnectionPoint,
0xb196b286, 0xbab4, 0x101a,
0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07);
DEFINE_GUID(IID_IConnectionPointContainer,
0xb196b284, 0xbab4, 0x101a,
0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07);
DEFINE_GUID(IID_IPictureDisp,
0x7bf80981, 0xbf32, 0x101a,
0x8b, 0xbb, 0x00, 0xaa, 0x00, 0x30, 0x0c, 0xab);
DEFINE_GUID(IID_ApplicationEvents, 0x0006304E, 0x0000, 0x0000,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(IID_ExplorerEvents, 0x0006300F, 0x0000, 0x0000,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(IID_ExplorersEvents, 0x00063078, 0x0000, 0x0000,
0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(IID_MailItemEvents, 0x0006302B, 0x0000, 0x0000,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(IID_MailItem, 0x00063034, 0x0000, 0x0000,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(IID_IMAPISecureMessage, 0x253cc320, 0xeab6, 0x11d0,
0x82, 0x22, 0, 0x60, 0x97, 0x93, 0x87, 0xea);
DEFINE_OLEGUID(IID_IUnknown, 0x00000000, 0, 0);
DEFINE_OLEGUID(IID_IDispatch, 0x00020400, 0, 0);
DEFINE_OLEGUID(IID_IOleWindow, 0x00000114, 0, 0);
#ifndef PR_SMTP_ADDRESS_DASL
#define PR_SMTP_ADDRESS_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
#endif
#ifndef PR_EMS_AB_PROXY_ADDRESSES_DASL
#define PR_EMS_AB_PROXY_ADDRESSES_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x800F101E"
#endif
#ifndef PR_ATTACHMENT_HIDDEN_DASL
#define PR_ATTACHMENT_HIDDEN_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
#endif
#define PR_MESSAGE_CLASS_W_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x001A001F"
#define GPGOL_ATTACHTYPE_DASL \
"http://schemas.microsoft.com/mapi/string/" \
"{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL Attach Type/0x00000003"
#define GPGOL_UID_DASL \
"http://schemas.microsoft.com/mapi/string/" \
"{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F"
#define PR_ATTACH_DATA_BIN_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x37010102"
#define PR_BODY_W_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x1000001F"
#define PR_ATTACHMENT_HIDDEN_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
#define PR_ATTACH_MIME_TAG_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x370E001F"
#define PR_ATTACH_CONTENT_ID_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x3712001F"
#define PR_TAG_SENDER_SMTP_ADDRESS \
"http://schemas.microsoft.com/mapi/proptag/0x5D01001F"
#define PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS \
"http://schemas.microsoft.com/mapi/proptag/0x5D08001F"
#define PR_PIDNameContentType_DASL \
"http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/content-type/0x0000001F"
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
/* Return the malloced name of an COM+ object. */
char *get_object_name (LPUNKNOWN obj);
/* Helper to lookup a dispid. */
DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name);
/* Return the OOM object's IDispatch interface described by FULLNAME. */
LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname);
/* Set the Icon of a CommandBarControl. */
int put_oom_icon (LPDISPATCH pDisp, int rsource_id, int size);
/* Set the boolean property NAME to VALUE. */
int put_oom_bool (LPDISPATCH pDisp, const char *name, int value);
/* Set the property NAME to VALUE. */
int put_oom_int (LPDISPATCH pDisp, const char *name, int value);
/* Set the property NAME to STRING. */
int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string);
/* Set the property NAME to DISP. */
int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH value);
/* Get the boolean property NAME of the object PDISP. */
int get_oom_bool (LPDISPATCH pDisp, const char *name);
/* Get the integer property NAME of the object PDISP. */
int get_oom_int (LPDISPATCH pDisp, const char *name);
/* Get the string property NAME of the object PDISP. */
char *get_oom_string (LPDISPATCH pDisp, const char *name);
/* Get an IUnknown object from property NAME of PDISP. */
LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name);
/* Return the control object with tag property value TAG. */
LPDISPATCH get_oom_control_bytag (LPDISPATCH pObj, const char *tag);
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
LPDISPATCH add_oom_button (LPDISPATCH pObj);
/* Delete a button. */
void del_oom_button (LPDISPATCH button);
/* Get the HWND of the active window in the current context */
HWND get_oom_context_window (LPDISPATCH context);
/* Get the address of the recipients as string list */
char ** get_oom_recipients (LPDISPATCH recipients);
/* Add an attachment to a dispatcher */
int
add_oom_attachment (LPDISPATCH disp, const wchar_t* inFile,
const wchar_t *displayName);
/* Look up a string with the propertyAccessor interface */
char *
get_pa_string (LPDISPATCH pDisp, const char *property);
/* Look up a long with the propertyAccessor interface.
returns -1 on error.*/
int
get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt);
/* Set a variant with the propertyAccessor interface.
This is tested to work at least vor BSTR variants. Trying
to set PR_ATTACH_DATA_BIN_DASL with this failed with
hresults 0x80020005 type mismatch or 0x80020008 vad
variable type for:
VT_ARRAY | VT_UI1 | VT_BYREF
VT_SAFEARRAY | VT_UI1 | VT_BYREF
VT_BSTR | VT_BYREF
VT_BSTR
VT_ARRAY | VT_UI1
VT_SAFEARRAY | VT_UI1
No idea whats wrong there. Needs more experiments. The
Type is only documented as "Binary". Outlookspy also
fails with the same error when trying to modify the
property.
*/
int
put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value);
int
put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value);
int
put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value);
/* Look up a variant with the propertyAccessor interface */
int
get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant);
/* Look up a LONG with the propertyAccessor interface */
LONG
get_pa_long (LPDISPATCH pDisp, const char *dasl_id);
/* Queries the interface of the dispatcher for the id
id. Returns NULL on error. The returned Object
must be released.
Mainly useful to check if an object is what
it appears to be. */
LPDISPATCH
get_object_by_id (LPDISPATCH pDisp, REFIID id);
/* Obtain the MAPI Message corresponding to the
Mailitem. Returns NULL on error.
The returned Message needs to be released by the
caller */
LPMESSAGE
get_oom_message (LPDISPATCH mailitem);
/* Obtain the Base MAPI Message of a MailItem.
The parameter should be a pointer to a MailItem.
returns NULL on error.
The returned Message needs to be released by the
caller.
*/
LPMESSAGE
get_oom_base_message (LPDISPATCH mailitem);
/* Get a strong reference for a mail object by calling
Application.GetObjectReference with type strong. The
documentation is unclear what this acutally does.
This function is left over from experiments about
strong references. Maybe there is a use for them.
The reference we use in the Mail object is documented
as a Weak reference. But changing that does not appear
to make a difference.
*/
LPDISPATCH
get_strong_reference (LPDISPATCH mail);
/* Invoke a method of an outlook object.
returns true on success false otherwise.
rVariant should either point to a propery initialized
variant (initinalized wiht VariantInit) to hold
the return value or a pointer to NULL.
*/
int
invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant);
/* Invoke a method of an outlook object.
returns true on success false otherwise.
rVariant should either point to a propery initialized
variant (initinalized wiht VariantInit) to hold
the return value or a pointer to NULL.
parms can optionally be used to provide a DISPPARAMS structure
with parameters for the function.
*/
int
invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params);
/* Try to obtain the mapisession through the Application.
returns NULL on error.*/
LPMAPISESSION
get_oom_mapi_session (void);
/* Ensure a category of the name name exists in
the session for the Mail mail.
Creates the category with the specified color if required.
returns 0 on success. */
void
ensure_category_exists (LPDISPATCH mail, const char *category, int color);
/* Add a category to a mail if it is not already added. */
int
add_category (LPDISPATCH mail, const char *category);
/* Remove a category from a mail if it was added. */
int
remove_category (LPDISPATCH mail, const char *category);
/* Get a unique identifier for a mail object. The
uuid is a custom property. If create is set
a new uuid will be added if none exists and the
value of that uuid returned.
The optinal uuid value can be set to be used
as uuid instead of a generated one.
Return value has to be freed by the caller.
*/
char *
get_unique_id (LPDISPATCH mail, int create, const char* uuid);
/* Uses the Application->ActiveWindow to determine the hwnd
through FindWindow and the caption. Does not use IOleWindow
because that was unreliable somhow. */
HWND get_active_hwnd (void);
/* Create a new mailitem and return it */
LPDISPATCH create_mail (void);
LPDISPATCH get_account_for_mail (const char *mbox);
/* Sender fallbacks. All return either null or a malloced address. */
char *get_sender_CurrentUser (LPDISPATCH mailitem);
char *get_sender_Sender (LPDISPATCH mailitem);
char *get_sender_SenderEMailAddress (LPDISPATCH mailitem);
/* Get the body of the active inline response */
char *get_inline_body (void);
+
+/* Get the major version of the exchange server of the account for the
+ mail address "mbox". Returns -1 if no version could be detected
+ or exchange is not used.*/
+int get_ex_major_version_for_addr (const char *mbox);
+
#ifdef __cplusplus
char *get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite);
}
#endif
#endif /*OOMHELP_H*/