diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp index 2e9829b..7f701b2 100644 --- a/src/cryptcontroller.cpp +++ b/src/cryptcontroller.cpp @@ -1,1059 +1,1070 @@ /* @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 "mymapitags.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__); gpgol_message_box (m_mail->get_window(), _("No recipients for encryption selected."), _("GpgOL"), MB_OK); return -2; } 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 (GpgME::Error &err) { 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); const auto err1 = result_pair.first.error(); const auto err2 = result_pair.second.error(); if (err1 || err2) { log_error ("%s:%s: Encrypt / Sign error %s %s.", SRCNAME, __func__, result_pair.first.error().asString(), result_pair.second.error().asString()); err = err1 ? err1 : err2; return -1; } if (err1.isCanceled() || err2.isCanceled()) { err = err1.isCanceled() ? err1 : err2; 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); err = sigResult.error(); if (err) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, sigResult.error().asString()); return -1; } if (err.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); err = encResult.error(); if (err) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, err.asString()); return -1; } if (err.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); err = result.error(); if (err) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, err.asString()); return -1; } if (err.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); err = result.error(); if (err) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, err.asString()); return -1; } if (err.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, int exchange_major_version) { char boundary[BOUNDARYSIZE+1]; int rc = create_top_encryption_header (sink, protocol, boundary, 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; } 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; } + if (m_mail->do_pgp_inline ()) + { + // Nothing to do for inline. + log_debug ("%s:%s: Inline mail. Setting encoding.", + SRCNAME, __func__); + + SPropValue prop; + prop.ulPropTag = PR_INTERNET_CPID; + prop.Value.l = 65001; + if (HrSetOneProp (message, &prop)) + { + log_error ("%s:%s: Failed to set CPID mapiprop.", + SRCNAME, __func__); + } + + return 0; + } + 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 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 && 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, exchange_major_version); } else if (m_encrypt) { 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/mail.cpp b/src/mail.cpp index 4bdf8a6..b2d0fbe 100644 --- a/src/mail.cpp +++ b/src/mail.cpp @@ -1,3095 +1,3098 @@ /* @file mail.h * @brief High level class to work with Outlook Mailitems. * * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "dialogs.h" #include "common.h" #include "mail.h" #include "eventsinks.h" #include "attachment.h" #include "mapihelp.h" #include "mimemaker.h" #include "revert.h" #include "gpgoladdin.h" #include "mymapitags.h" #include "parsecontroller.h" #include "cryptcontroller.h" #include "windowmessages.h" #include "mlang-charset.h" #include "wks-helper.h" #include "keycache.h" #include "cpphelp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #undef _ #define _(a) utf8_gettext (a) using namespace GpgME; static std::map s_mail_map; static std::map s_uid_map; static std::set uids_searched; static Mail *s_last_mail; #define COPYBUFSIZE (8 * 1024) Mail::Mail (LPDISPATCH mailitem) : m_mailitem(mailitem), m_processed(false), m_needs_wipe(false), m_needs_save(false), m_crypt_successful(false), m_is_smime(false), m_is_smime_checked(false), m_is_signed(false), m_is_valid(false), m_close_triggered(false), m_is_html_alternative(false), m_needs_encrypt(false), m_moss_position(0), m_crypto_flags(0), m_cached_html_body(nullptr), m_cached_plain_body(nullptr), m_cached_recipients(nullptr), m_type(MSGTYPE_UNKNOWN), m_do_inline(false), m_is_gsuite(false), m_crypt_state(NoCryptMail), m_window(nullptr), m_async_crypt_disabled(false), m_is_forwarded_crypto_mail(false), m_is_send_again(false), m_disable_att_remove_warning(false) { if (get_mail_for_item (mailitem)) { log_error ("Mail object for item: %p already exists. Bug.", mailitem); return; } m_event_sink = install_MailItemEvents_sink (mailitem); if (!m_event_sink) { /* Should not happen but in that case we don't add us to the list and just release the Mail item. */ log_error ("%s:%s: Failed to install MailItemEvents sink.", SRCNAME, __func__); gpgol_release(mailitem); return; } s_mail_map.insert (std::pair (mailitem, this)); s_last_mail = this; } GPGRT_LOCK_DEFINE(dtor_lock); Mail::~Mail() { /* This should fix a race condition where the mail is deleted before the parser is accessed in the decrypt thread. The shared_ptr of the parser then ensures that the parser is alive even if the mail is deleted while parsing. */ gpgrt_lock_lock (&dtor_lock); log_oom_extra ("%s:%s: dtor: Mail: %p item: %p", SRCNAME, __func__, this, m_mailitem); std::map::iterator it; log_oom_extra ("%s:%s: Detaching event sink", SRCNAME, __func__); detach_MailItemEvents_sink (m_event_sink); gpgol_release(m_event_sink); log_oom_extra ("%s:%s: Erasing mail", SRCNAME, __func__); it = s_mail_map.find(m_mailitem); if (it != s_mail_map.end()) { s_mail_map.erase (it); } if (!m_uuid.empty()) { auto it2 = s_uid_map.find(m_uuid); if (it2 != s_uid_map.end()) { s_uid_map.erase (it2); } } log_oom_extra ("%s:%s: releasing mailitem", SRCNAME, __func__); gpgol_release(m_mailitem); xfree (m_cached_html_body); xfree (m_cached_plain_body); release_cArray (m_cached_recipients); if (!m_uuid.empty()) { log_oom_extra ("%s:%s: destroyed: %p uuid: %s", SRCNAME, __func__, this, m_uuid.c_str()); } else { log_oom_extra ("%s:%s: non crypto (or sent) mail: %p destroyed", SRCNAME, __func__, this); } log_oom_extra ("%s:%s: nulling shared pointer", SRCNAME, __func__); m_parser = nullptr; m_crypter = nullptr; gpgrt_lock_unlock (&dtor_lock); log_oom_extra ("%s:%s: returning", SRCNAME, __func__); } Mail * Mail::get_mail_for_item (LPDISPATCH mailitem) { if (!mailitem) { return NULL; } std::map::iterator it; it = s_mail_map.find(mailitem); if (it == s_mail_map.end()) { return NULL; } return it->second; } Mail * Mail::get_mail_for_uuid (const char *uuid) { if (!uuid) { return NULL; } auto it = s_uid_map.find(std::string(uuid)); if (it == s_uid_map.end()) { return NULL; } return it->second; } bool Mail::is_valid_ptr (const Mail *mail) { auto it = s_mail_map.begin(); while (it != s_mail_map.end()) { if (it->second == mail) return true; ++it; } return false; } int Mail::pre_process_message () { LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); return 0; } log_oom_extra ("%s:%s: GetBaseMessage OK for %p.", SRCNAME, __func__, m_mailitem); /* Change the message class here. It is important that we change the message class in the before read event regardless if it is already set to one of GpgOL's message classes. Changing the message class (even if we set it to the same value again that it already has) causes Outlook to reconsider what it "knows" about a message and reread data from the underlying base message. */ mapi_change_message_class (message, 1, &m_type); if (m_type == MSGTYPE_UNKNOWN) { gpgol_release (message); return 0; } /* Create moss attachments here so that they are properly hidden when the item is read into the model. */ m_moss_position = mapi_mark_or_create_moss_attach (message, m_type); if (!m_moss_position) { log_error ("%s:%s: Failed to find moss attachment.", SRCNAME, __func__); m_type = MSGTYPE_UNKNOWN; } gpgol_release (message); return 0; } static LPDISPATCH get_attachment (LPDISPATCH mailitem, int pos) { LPDISPATCH attachment; LPDISPATCH attachments = get_oom_object (mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); return NULL; } std::string item_str; int count = get_oom_int (attachments, "Count"); if (count < 1) { log_debug ("%s:%s: Invalid attachment count: %i.", SRCNAME, __func__, count); gpgol_release (attachments); return NULL; } if (pos > 0) { item_str = std::string("Item(") + std::to_string(pos) + ")"; } else { item_str = std::string("Item(") + std::to_string(count) + ")"; } attachment = get_oom_object (attachments, item_str.c_str()); gpgol_release (attachments); return attachment; } /** Helper to check that all attachments are hidden, to be called before crypto. */ int Mail::check_attachments () const { LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); return 1; } int count = get_oom_int (attachments, "Count"); if (!count) { gpgol_release (attachments); return 0; } std::string message; if (is_encrypted () && is_signed ()) { message += _("Not all attachments were encrypted or signed.\n" "The unsigned / unencrypted attachments are:\n\n"); } else if (is_signed ()) { message += _("Not all attachments were signed.\n" "The unsigned attachments are:\n\n"); } else if (is_encrypted ()) { message += _("Not all attachments were encrypted.\n" "The unencrypted attachments are:\n\n"); } else { gpgol_release (attachments); return 0; } bool foundOne = false; for (int i = 1; i <= count; i++) { std::string item_str; item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ()); if (!oom_attach) { log_error ("%s:%s: Failed to get attachment.", SRCNAME, __func__); continue; } VARIANT var; VariantInit (&var); if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var) || (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE)) { foundOne = true; char *dispName = get_oom_string (oom_attach, "DisplayName"); message += dispName ? dispName : "Unknown"; xfree (dispName); message += "\n"; } VariantClear (&var); gpgol_release (oom_attach); } gpgol_release (attachments); if (foundOne) { message += "\n"; message += _("Note: The attachments may be encrypted or signed " "on a file level but the GpgOL status does not apply to them."); wchar_t *wmsg = utf8_to_wchar (message.c_str ()); wchar_t *wtitle = utf8_to_wchar (_("GpgOL Warning")); MessageBoxW (get_active_hwnd (), wmsg, wtitle, MB_ICONWARNING|MB_OK); xfree (wmsg); xfree (wtitle); } return 0; } /** Get the cipherstream of the mailitem. */ static LPSTREAM get_attachment_stream (LPDISPATCH mailitem, int pos) { if (!pos) { log_debug ("%s:%s: Called with zero pos.", SRCNAME, __func__); return NULL; } LPDISPATCH attachment = get_attachment (mailitem, pos); LPSTREAM stream = NULL; if (!attachment) { // For opened messages that have ms-tnef type we // create the moss attachment but don't find it // in the OOM. Try to find it through MAPI. HRESULT hr; log_debug ("%s:%s: Failed to find MOSS Attachment. " "Fallback to MAPI.", SRCNAME, __func__); LPMESSAGE message = get_oom_message (mailitem); if (!message) { log_debug ("%s:%s: Failed to get MAPI Interface.", SRCNAME, __func__); return NULL; } hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); return NULL; } return stream; } LPATTACH mapi_attachment = NULL; mapi_attachment = (LPATTACH) get_oom_iunknown (attachment, "MapiObject"); gpgol_release (attachment); if (!mapi_attachment) { log_debug ("%s:%s: Failed to get MapiObject of attachment: %p", SRCNAME, __func__, attachment); return NULL; } if (FAILED (mapi_attachment->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_MODIFY, (LPUNKNOWN*) &stream))) { log_debug ("%s:%s: Failed to open stream for mapi_attachment: %p", SRCNAME, __func__, mapi_attachment); gpgol_release (mapi_attachment); } return stream; } #if 0 This should work. But Outlook says no. See the comment in set_pa_variant about this. I left the code here as an example how to work with safearrays and how this probably should work. static int copy_data_property(LPDISPATCH target, std::shared_ptr attach) { VARIANT var; VariantInit (&var); /* Get the size */ off_t size = attach->get_data ().seek (0, SEEK_END); attach->get_data ().seek (0, SEEK_SET); if (!size) { TRACEPOINT; return 1; } if (!get_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { log_debug("Have variant. type: %x", var.vt); } else { log_debug("failed to get variant."); } /* Set the type to an array of unsigned chars (OLE SAFEARRAY) */ var.vt = VT_ARRAY | VT_UI1; /* Set up the bounds structure */ SAFEARRAYBOUND rgsabound[1]; rgsabound[0].cElements = static_cast (size); rgsabound[0].lLbound = 0; /* Create an OLE SAFEARRAY */ var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound); if (var.parray == NULL) { TRACEPOINT; VariantClear(&var); return 1; } void *buffer = NULL; /* Get a safe pointer to the array */ if (SafeArrayAccessData(var.parray, &buffer) != S_OK) { TRACEPOINT; VariantClear(&var); return 1; } /* Copy data to it */ size_t nread = attach->get_data ().read (buffer, static_cast (size)); if (nread != static_cast (size)) { TRACEPOINT; VariantClear(&var); return 1; } /*/ Unlock the variant data */ if (SafeArrayUnaccessData(var.parray) != S_OK) { TRACEPOINT; VariantClear(&var); return 1; } if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { TRACEPOINT; VariantClear(&var); return 1; } VariantClear(&var); return 0; } #endif static int copy_attachment_to_file (std::shared_ptr att, HANDLE hFile) { char copybuf[COPYBUFSIZE]; size_t nread; /* Security considerations: Writing the data to a temporary file is necessary as neither MAPI manipulation works in the read event to transmit the data nor Property Accessor works (see above). From a security standpoint there is a short time where the temporary files are on disk. Tempdir should be protected so that only the user can read it. Thus we have a local attack that could also take the data out of Outlook. FILE_SHARE_READ is necessary so that outlook can read the file. A bigger concern is that the file is manipulated by another software to fake the signature state. So we keep the write exlusive to us. We delete the file before closing the write file handle. */ /* Make sure we start at the beginning */ att->get_data ().seek (0, SEEK_SET); while ((nread = att->get_data ().read (copybuf, COPYBUFSIZE))) { DWORD nwritten; if (!WriteFile (hFile, copybuf, nread, &nwritten, NULL)) { log_error ("%s:%s: Failed to write in tmp attachment.", SRCNAME, __func__); return 1; } if (nread != nwritten) { log_error ("%s:%s: Write truncated.", SRCNAME, __func__); return 1; } } return 0; } /** Sets some meta data on the last attachment added. The meta data is taken from the attachment object. */ static int fixup_last_attachment (LPDISPATCH mail, std::shared_ptr attachment) { /* Currently we only set content id */ if (attachment->get_content_id ().empty()) { log_debug ("%s:%s: Content id not found.", SRCNAME, __func__); return 0; } LPDISPATCH attach = get_attachment (mail, -1); if (!attach) { log_error ("%s:%s: No attachment.", SRCNAME, __func__); return 1; } int ret = put_pa_string (attach, PR_ATTACH_CONTENT_ID_DASL, attachment->get_content_id ().c_str()); gpgol_release (attach); return ret; } /** Helper to update the attachments of a mail object in oom. does not modify the underlying mapi structure. */ static int add_attachments(LPDISPATCH mail, std::vector > attachments) { int err = 0; for (auto att: attachments) { if (att->get_display_name().empty()) { log_error ("%s:%s: Ignoring attachment without display name.", SRCNAME, __func__); continue; } wchar_t* wchar_name = utf8_to_wchar (att->get_display_name().c_str()); HANDLE hFile; wchar_t* wchar_file = get_tmp_outfile (wchar_name, &hFile); if (copy_attachment_to_file (att, hFile)) { log_error ("%s:%s: Failed to copy attachment %s to temp file", SRCNAME, __func__, att->get_display_name().c_str()); err = 1; } if (add_oom_attachment (mail, wchar_file, wchar_name)) { log_error ("%s:%s: Failed to add attachment: %s", SRCNAME, __func__, att->get_display_name().c_str()); err = 1; } CloseHandle (hFile); if (!DeleteFileW (wchar_file)) { log_error ("%s:%s: Failed to delete tmp attachment for: %s", SRCNAME, __func__, att->get_display_name().c_str()); err = 1; } xfree (wchar_file); xfree (wchar_name); err = fixup_last_attachment (mail, att); } return err; } GPGRT_LOCK_DEFINE(parser_lock); static DWORD WINAPI do_parsing (LPVOID arg) { gpgrt_lock_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: canceling parsing for: %p already deleted", SRCNAME, __func__, arg); gpgrt_lock_unlock (&dtor_lock); return 0; } /* This takes a shared ptr of parser. So the parser is still valid when the mail is deleted. */ auto parser = mail->parser(); gpgrt_lock_unlock (&dtor_lock); gpgrt_lock_lock (&parser_lock); gpgrt_lock_lock (&invalidate_lock); /* We lock the parser here to avoid too many decryption attempts if there are multiple mailobjects which might have already been deleted (e.g. by quick switches of the mailview.) Let's rather be a bit slower. */ log_debug ("%s:%s: preparing the parser for: %p", SRCNAME, __func__, arg); if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: cancel for: %p already deleted", SRCNAME, __func__, arg); gpgrt_lock_unlock (&invalidate_lock); gpgrt_lock_unlock (&parser_lock); return 0; } if (!parser) { log_error ("%s:%s: no parser found for mail: %p", SRCNAME, __func__, arg); gpgrt_lock_unlock (&invalidate_lock); gpgrt_lock_unlock (&parser_lock); return -1; } parser->parse(); do_in_ui_thread (PARSING_DONE, arg); gpgrt_lock_unlock (&invalidate_lock); gpgrt_lock_unlock (&parser_lock); return 0; } /* How encryption is done: There are two modes of encryption. Synchronous and Async. If async is used depends on the value of mail->async_crypt_disabled. Synchronous crypto: > Send Event < | State NoCryptMail Needs Crypto ? (get_gpgol_draft_info_flags != 0) -> No: Pass send -> unencrypted mail. -> Yes: mail->update_oom_data State = Mail::NeedsFirstAfterWrite check_inline_response invoke_oom_method (m_object, "Save", NULL); > Write Event < Pass because is_crypto_mail is false (not a decrypted mail) > AfterWrite Event < | State NeedsFirstAfterWrite State = NeedsActualCrypo encrypt_sign_start collect_input_data -> Check if Inline PGP should be used do_crypt -> Resolve keys / do crypto State = NeedsUpdateInMAPI update_crypt_mapi crypter->update_mail_mapi if (inline) (Meaning PGP/Inline) <-- do nothing. else build MSOXSMIME attachment and clear body / attachments. State = NeedsUpdateInOOM <- Back to Send Event update_crypt_oom -> Cleans body or sets PGP/Inline body. (inline_body_to_body) State = WantsSendMIME or WantsSendInline -> Saftey check "has_crypted_or_empty_body" -> If MIME Mail do the T3656 check. Send. State order for "inline_response" (sync) Mails. NoCryptMail NeedsFirstAfterWrite NeedsActualCrypto NeedsUpdateInMAPI NeedsUpdateInOOM WantsSendMIME (or inline for PGP Inline) -> Send. State order for async Mails NoCryptMail NeedsFirstAfterWrite NeedsActualCrypto -> Cancel Send. Windowmessages -> Crypto Done NeedsUpdateInOOM NeedsSecondAfterWrite trigger Save. NeedsUpdateInMAPI WantsSendMIME trigger Send. */ static DWORD WINAPI do_crypt (LPVOID arg) { gpgrt_lock_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: canceling crypt for: %p already deleted", SRCNAME, __func__, arg); gpgrt_lock_unlock (&dtor_lock); return 0; } if (mail->crypt_state() != Mail::NeedsActualCrypt) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, mail->crypt_state ()); mail->set_window_enabled (true); gpgrt_lock_unlock (&dtor_lock); return -1; } /* This takes a shared ptr of crypter. So the crypter is still valid when the mail is deleted. */ auto crypter = mail->crypter(); gpgrt_lock_unlock (&dtor_lock); if (!crypter) { log_error ("%s:%s: no crypter found for mail: %p", SRCNAME, __func__, arg); gpgrt_lock_unlock (&parser_lock); mail->set_window_enabled (true); return -1; } GpgME::Error err; int rc = crypter->do_crypto(err); gpgrt_lock_lock (&dtor_lock); if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: aborting crypt for: %p already deleted", SRCNAME, __func__, arg); gpgrt_lock_unlock (&dtor_lock); return 0; } mail->set_window_enabled (true); if (rc == -1 || err) { mail->reset_crypter (); crypter = nullptr; if (err) { char *buf = nullptr; gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"), err.asString()); gpgol_message_box (mail->get_window(), buf, _("GpgOL"), MB_OK); xfree (buf); } else { gpgol_bug (mail->get_window (), ERR_CRYPT_RESOLVER_FAILED); } } if (rc || err.isCanceled()) { log_debug ("%s:%s: crypto failed for: %p with: %i err: %i", SRCNAME, __func__, arg, rc, err.code()); mail->set_crypt_state (Mail::NoCryptMail); mail->reset_crypter (); crypter = nullptr; gpgrt_lock_unlock (&dtor_lock); return rc; } if (!mail->async_crypt_disabled ()) { mail->set_crypt_state (Mail::NeedsUpdateInOOM); gpgrt_lock_unlock (&dtor_lock); // This deletes the Mail in Outlook 2010 do_in_ui_thread (CRYPTO_DONE, arg); log_debug ("%s:%s: UI thread finished for %p", SRCNAME, __func__, arg); } else { mail->set_crypt_state (Mail::NeedsUpdateInMAPI); mail->update_crypt_mapi (); if (mail->crypt_state () == Mail::WantsSendMIME) { // For sync crypto we need to switch this. mail->set_crypt_state (Mail::NeedsUpdateInOOM); } else { // A bug! log_debug ("%s:%s: Resetting crypter because of state mismatch. %p", SRCNAME, __func__, arg); crypter = nullptr; mail->reset_crypter (); } gpgrt_lock_unlock (&dtor_lock); } /* This works around a bug in pinentry that it might bring the wrong window to front. So after encryption / signing we bring outlook back to front. See GnuPG-Bug-Id: T3732 */ do_in_ui_thread_async (BRING_TO_FRONT, nullptr); log_debug ("%s:%s: crypto thread for %p finished", SRCNAME, __func__, arg); return 0; } bool Mail::is_crypto_mail() const { if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL || m_type == MSGTYPE_SMIME) { /* Not a message for us. */ return false; } return true; } int Mail::decrypt_verify() { if (!is_crypto_mail()) { log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.", SRCNAME, __func__, m_mailitem); return 0; } if (m_needs_wipe) { log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p", SRCNAME, __func__, m_mailitem); return 1; } set_uuid (); m_processed = true; /* Insert placeholder */ char *placeholder_buf; if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, "OpenPGP", _("Pubkey directory confirmation"), _("This is a confirmation request to publish your Pubkey in the " "directory for your domain.\n\n" "

If you did not request to publish your Pubkey in your providers " "directory, simply ignore this message.

\n")); } else if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, is_smime() ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being decrypted / verified...")) == -1) { log_error ("%s:%s: Failed to format placeholder.", SRCNAME, __func__); return 1; } if (opt.prefer_html) { m_orig_body = get_oom_string (m_mailitem, "HTMLBody"); if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf)) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } else { m_orig_body = get_oom_string (m_mailitem, "Body"); if (put_oom_string (m_mailitem, "Body", placeholder_buf)) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } } xfree (placeholder_buf); /* Do the actual parsing */ auto cipherstream = get_attachment_stream (m_mailitem, m_moss_position); if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { WKSHelper::instance ()->handle_confirmation_read (this, cipherstream); return 0; } if (!cipherstream) { log_debug ("%s:%s: Failed to get cipherstream.", SRCNAME, __func__); return 1; } m_parser = std::shared_ptr (new ParseController (cipherstream, m_type)); m_parser->setSender(GpgME::UserID::addrSpecFromString(get_sender().c_str())); log_mime_parser ("%s:%s: Parser for \"%s\" is %p", SRCNAME, __func__, get_subject ().c_str(), m_parser.get()); gpgol_release (cipherstream); HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0, NULL); if (!parser_thread) { log_error ("%s:%s: Failed to create decrypt / verify thread.", SRCNAME, __func__); } CloseHandle (parser_thread); return 0; } void find_and_replace(std::string& source, const std::string &find, const std::string &replace) { for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;) { source.replace(i, find.length(), replace); i += replace.length(); } } void Mail::update_body() { if (!m_parser) { TRACEPOINT; return; } const auto error = m_parser->get_formatted_error (); if (!error.empty()) { if (opt.prefer_html) { if (put_oom_string (m_mailitem, "HTMLBody", error.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } else { log_debug ("%s:%s: Set error html to: '%s'", SRCNAME, __func__, error.c_str ()); } } else { if (put_oom_string (m_mailitem, "Body", error.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } else { log_debug ("%s:%s: Set error plain to: '%s'", SRCNAME, __func__, error.c_str ()); } } return; } if (m_verify_result.error()) { log_error ("%s:%s: Verification failed. Restoring Body.", SRCNAME, __func__); if (opt.prefer_html) { if (put_oom_string (m_mailitem, "HTMLBody", m_orig_body.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } else { if (put_oom_string (m_mailitem, "Body", m_orig_body.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } return; } // No need to carry body anymore m_orig_body = std::string(); auto html = m_parser->get_html_body (); /** Outlook does not show newlines if \r\r\n is a newline. We replace these as apparently some other buggy MUA sends this. */ find_and_replace (html, "\r\r\n", "\r\n"); if (opt.prefer_html && !html.empty() && !m_block_html) { const auto charset = m_parser->get_html_charset(); int codepage = 0; - if (html_charset.empty()) + if (charset.empty()) { codepage = get_oom_int (m_mailitem, "InternetCodepage"); log_debug ("%s:%s: Did not find html charset." " Using internet Codepage %i.", SRCNAME, __func__, codepage); } char *converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(), html.size(), codepage); int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } return; } auto body = m_parser->get_body (); if (body.empty () && m_block_html && !html.empty()) { #if 0 Sadly the following code still offers to load external references it might also be too dangerous if Outlook somehow autoloads the references as soon as the Body is put into HTML // Fallback to show HTML as plaintext if HTML display // is blocked. log_error ("%s:%s: No text body. Putting HTML into plaintext.", SRCNAME, __func__); char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(), html.c_str(), html.size()); int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); body = html; } else { char *plainBody = get_oom_string (m_mailitem, "Body"); if (!plainBody) { log_error ("%s:%s: Failed to obtain converted plain body.", SRCNAME, __func__); body = html; } else { ret = put_oom_string (m_mailitem, "HTMLBody", plainBody); xfree (plainBody); if (ret) { log_error ("%s:%s: Failed to put plain into html body of item.", SRCNAME, __func__); body = html; } else { return; } } } #endif body = html; std::string buf = _("HTML display disabled."); buf += "\n\n"; buf += _("For security reasons HTML content in unsigned, encrypted\n" "S/MIME mails cannot be displayed.\n\n" "Please ask the sender to sign the message or to send it as plain text."); gpgol_message_box (get_window(), buf.c_str() , _("GpgOL"), MB_OK); } find_and_replace (body, "\r\r\n", "\r\n"); const auto plain_charset = m_parser->get_body_charset(); int codepage = 0; if (plain_charset.empty()) { codepage = get_oom_int (m_mailitem, "InternetCodepage"); log_debug ("%s:%s: Did not find body charset. " "Using internet Codepage %i.", SRCNAME, __func__, codepage); } char *converted = ansi_charset_to_utf8 (plain_charset.c_str(), body.c_str(), body.size(), codepage); int ret = put_oom_string (m_mailitem, "Body", converted ? converted : ""); xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } return; } void Mail::parsing_done() { TRACEPOINT; log_oom_extra ("Mail %p Parsing done for parser: %p", this, m_parser.get()); if (!m_parser) { /* This should not happen but it happens when outlook sends multiple ItemLoad events for the same Mail Object. In that case it could happen that one parser was already done while a second is now returning for the wrong mail (as it's looked up by uuid.) We have a check in get_uuid that the uuid was not in the map before (and the parser is replaced). So this really really should not happen. We handle it anyway as we crash otherwise. It should not happen because the parser is only created in decrypt_verify which is called in the read event. And even in there we check if the parser was set. */ log_error ("%s:%s: No parser obj. For mail: %p", SRCNAME, __func__, this); return; } /* Store the results. */ m_decrypt_result = m_parser->decrypt_result (); m_verify_result = m_parser->verify_result (); m_crypto_flags = 0; if (!m_decrypt_result.isNull()) { m_crypto_flags |= 1; } if (m_verify_result.numSignatures()) { m_crypto_flags |= 2; } update_sigstate (); m_needs_wipe = !m_is_send_again; TRACEPOINT; /* Set categories according to the result. */ update_categories (); TRACEPOINT; m_block_html = m_parser->shouldBlockHtml (); if (m_block_html) { // Just to be careful. set_block_status (); } TRACEPOINT; /* Update the body */ update_body(); TRACEPOINT; /* Check that there are no unsigned / unencrypted messages. */ check_attachments (); /* Update attachments */ if (add_attachments (m_mailitem, m_parser->get_attachments())) { log_error ("%s:%s: Failed to update attachments.", SRCNAME, __func__); } if (m_is_send_again) { log_debug ("%s:%s: I think that this is the send again of a crypto mail.", SRCNAME, __func__); /* We no longer want to be treated like a crypto mail. */ m_type = MSGTYPE_UNKNOWN; LPMESSAGE msg = get_oom_base_message (m_mailitem); if (!msg) { TRACEPOINT; } else { set_gpgol_draft_info_flags (msg, m_crypto_flags); gpgol_release (msg); } remove_our_attachments (); } log_debug ("%s:%s: Delayed invalidate to update sigstate.", SRCNAME, __func__); CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) this, 0, NULL)); TRACEPOINT; return; } int Mail::encrypt_sign_start () { if (m_crypt_state != NeedsActualCrypt) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); return -1; } int flags = 0; if (!needs_crypto()) { return 0; } LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); return -1; } flags = get_gpgol_draft_info_flags (message); gpgol_release (message); const auto window = get_active_hwnd (); if (m_is_gsuite) { auto att_table = mapi_create_attach_table (message, 0); int n_att_usable = count_usable_attachments (att_table); mapi_release_attach_table (att_table); /* Check for attachments if we have some abort. */ if (n_att_usable) { wchar_t *w_title = utf8_to_wchar (_( "GpgOL: Oops, G Suite Sync account detected")); wchar_t *msg = utf8_to_wchar ( _("G Suite Sync breaks outgoing crypto mails " "with attachments.\nUsing crypto and attachments " "with G Suite Sync is not supported.\n\n" "See: https://dev.gnupg.org/T3545 for details.")); MessageBoxW (window, msg, w_title, MB_ICONINFORMATION|MB_OK); xfree (msg); xfree (w_title); return -1; } } m_do_inline = m_is_gsuite ? true : opt.inline_pgp; GpgME::Protocol proto = opt.enable_smime ? GpgME::UnknownProtocol: GpgME::OpenPGP; m_crypter = std::shared_ptr (new CryptController (this, flags & 1, flags & 2, proto)); // Careful from here on we have to check every // error condition with window enabling again. set_window_enabled (false); if (m_crypter->collect_data ()) { log_error ("%s:%s: Crypter for mail %p failed to collect data.", SRCNAME, __func__, this); set_window_enabled (true); return -1; } if (!m_async_crypt_disabled) { CloseHandle(CreateThread (NULL, 0, do_crypt, (LPVOID) this, 0, NULL)); } else { do_crypt (this); } return 0; } int Mail::needs_crypto () { LPMESSAGE message = get_oom_message (m_mailitem); bool ret; if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); return false; } ret = get_gpgol_draft_info_flags (message); gpgol_release(message); return ret; } int Mail::wipe (bool force) { if (!m_needs_wipe && !force) { return 0; } log_debug ("%s:%s: Removing plaintext from mailitem: %p.", SRCNAME, __func__, m_mailitem); if (put_oom_string (m_mailitem, "HTMLBody", "")) { if (put_oom_string (m_mailitem, "Body", "")) { log_debug ("%s:%s: Failed to wipe mailitem: %p.", SRCNAME, __func__, m_mailitem); return -1; } return -1; } else { put_oom_string (m_mailitem, "Body", ""); } m_needs_wipe = false; return 0; } int Mail::update_oom_data () { char *buf = nullptr; log_debug ("%s:%s", SRCNAME, __func__); if (!is_crypto_mail()) { /* Update the body format. */ m_is_html_alternative = get_oom_int (m_mailitem, "BodyFormat") > 1; /* Store the body. It was not obvious for me (aheinecke) how to access this through MAPI. */ if (m_is_html_alternative) { log_debug ("%s:%s: Is html alternative mail.", SRCNAME, __func__); xfree (m_cached_html_body); m_cached_html_body = get_oom_string (m_mailitem, "HTMLBody"); } xfree (m_cached_plain_body); m_cached_plain_body = get_oom_string (m_mailitem, "Body"); release_cArray (m_cached_recipients); m_cached_recipients = get_recipients (); } /* For some reason outlook may store the recipient address in the send using account field. If we have SMTP we prefer the SenderEmailAddress string. */ if (is_crypto_mail ()) { /* This is the case where we are reading a mail and not composing. When composing we need to use the SendUsingAccount because if you send from the folder of userA but change the from to userB outlook will keep the SenderEmailAddress of UserA. This is all so horrible. */ buf = get_sender_SenderEMailAddress (m_mailitem); if (!buf) { /* Try the sender Object */ buf = get_sender_Sender (m_mailitem); } } if (!buf) { buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite); } if (!buf && !is_crypto_mail ()) { /* Try the sender Object */ buf = get_sender_Sender (m_mailitem); } if (!buf) { /* We don't have s sender object or SendUsingAccount, well, in that case fall back to the current user. */ buf = get_sender_CurrentUser (m_mailitem); } if (!buf) { log_debug ("%s:%s: All fallbacks failed.", SRCNAME, __func__); return -1; } m_sender = buf; xfree (buf); return 0; } std::string Mail::get_sender () { if (m_sender.empty()) update_oom_data(); return m_sender; } std::string Mail::get_cached_sender () { return m_sender; } int Mail::close_all_mails () { int err = 0; std::map::iterator it; TRACEPOINT; std::map mail_map_copy = s_mail_map; for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { /* XXX For non racy code the is_valid_ptr check should not be necessary but we crashed sometimes closing a destroyed mail. */ if (!is_valid_ptr (it->second)) { log_debug ("%s:%s: Already deleted mail for %p", SRCNAME, __func__, it->first); continue; } if (!it->second->is_crypto_mail()) { continue; } if (close_inspector (it->second) || close (it->second)) { log_error ("Failed to close mail: %p ", it->first); /* Should not happen */ if (is_valid_ptr (it->second) && it->second->revert()) { err++; } } } return err; } int Mail::revert_all_mails () { int err = 0; std::map::iterator it; for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it) { if (it->second->revert ()) { log_error ("Failed to revert mail: %p ", it->first); err++; continue; } it->second->set_needs_save (true); if (!invoke_oom_method (it->first, "Save", NULL)) { log_error ("Failed to save reverted mail: %p ", it->second); err++; continue; } } return err; } int Mail::wipe_all_mails () { int err = 0; std::map::iterator it; for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it) { if (it->second->wipe ()) { log_error ("Failed to wipe mail: %p ", it->first); err++; } } return err; } int Mail::revert () { int err = 0; if (!m_processed) { return 0; } m_disable_att_remove_warning = true; err = gpgol_mailitem_revert (m_mailitem); if (err == -1) { log_error ("%s:%s: Message revert failed falling back to wipe.", SRCNAME, __func__); return wipe (); } /* We need to reprocess the mail next time around. */ m_processed = false; m_needs_wipe = false; m_disable_att_remove_warning = false; return 0; } bool Mail::is_smime () { msgtype_t msgtype; LPMESSAGE message; if (m_is_smime_checked) { return m_is_smime; } message = get_oom_message (m_mailitem); if (!message) { log_error ("%s:%s: No message?", SRCNAME, __func__); return false; } msgtype = mapi_get_message_type (message); m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED || msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED; /* Check if it is an smime mail. Multipart signed can also be true. */ if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED) { char *proto; char *ct = mapi_get_message_content_type (message, &proto, NULL); if (ct && proto) { m_is_smime = (!strcmp (proto, "application/pkcs7-signature") || !strcmp (proto, "application/x-pkcs7-signature")); } else { log_error ("%s:%s: No protocol in multipart / signed mail.", SRCNAME, __func__); } xfree (proto); xfree (ct); } gpgol_release (message); m_is_smime_checked = true; return m_is_smime; } static std::string get_string (LPDISPATCH item, const char *str) { char *buf = get_oom_string (item, str); if (!buf) return std::string(); std::string ret = buf; xfree (buf); return ret; } std::string Mail::get_subject() const { return get_string (m_mailitem, "Subject"); } std::string Mail::get_body() const { return get_string (m_mailitem, "Body"); } std::string Mail::get_html_body() const { return get_string (m_mailitem, "HTMLBody"); } char ** Mail::get_recipients() const { LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients"); if (!recipients) { TRACEPOINT; return nullptr; } auto ret = get_oom_recipients (recipients); gpgol_release (recipients); return ret; } int Mail::close_inspector (Mail *mail) { LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector"); HRESULT hr; DISPID dispid; if (!inspector) { log_debug ("%s:%s: No inspector.", SRCNAME, __func__); return -1; } dispid = lookup_oom_dispid (inspector, "Close"); if (dispid != DISPID_UNKNOWN) { VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Failed to close inspector: %#lx", SRCNAME, __func__, hr); gpgol_release (inspector); return -1; } } gpgol_release (inspector); return 0; } /* static */ int Mail::close (Mail *mail) { VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; log_oom_extra ("%s:%s: Invoking close for: %p", SRCNAME, __func__, mail->item()); mail->set_close_triggered (true); int rc = invoke_oom_method_with_parms (mail->item(), "Close", NULL, &dispparams); log_oom_extra ("%s:%s: Returned from close", SRCNAME, __func__); return rc; } void Mail::set_close_triggered (bool value) { m_close_triggered = value; } bool Mail::get_close_triggered () const { return m_close_triggered; } static const UserID get_uid_for_sender (const Key &k, const char *sender) { UserID ret; if (!sender) { return ret; } if (!k.numUserIDs()) { log_debug ("%s:%s: Key without uids", SRCNAME, __func__); return ret; } for (const auto uid: k.userIDs()) { if (!uid.email()) { log_error ("%s:%s: skipping uid without email.", SRCNAME, __func__); continue; } auto normalized_uid = uid.addrSpec(); auto normalized_sender = UserID::addrSpecFromString(sender); if (normalized_sender.empty() || normalized_uid.empty()) { log_error ("%s:%s: normalizing '%s' or '%s' failed.", SRCNAME, __func__, uid.email(), sender); continue; } if (normalized_sender == normalized_uid) { ret = uid; } } return ret; } void Mail::update_sigstate () { std::string sender = get_sender(); if (sender.empty()) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return; } if (m_verify_result.isNull()) { log_debug ("%s:%s: No verify result.", SRCNAME, __func__); return; } if (m_verify_result.error()) { log_debug ("%s:%s: verify error.", SRCNAME, __func__); return; } for (const auto sig: m_verify_result.signatures()) { m_is_signed = true; m_uid = get_uid_for_sender (sig.key(), sender.c_str()); if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal && sig.validity() != Signature::Validity::Full && sig.validity() != Signature::Validity::Ultimate)) { /* For our category we only care about trusted sigs. And the UID needs to match.*/ continue; } if (sig.validity() == Signature::Validity::Marginal) { const auto tofu = m_uid.tofuInfo(); if (!tofu.isNull() && (tofu.validity() != TofuInfo::Validity::BasicHistory && tofu.validity() != TofuInfo::Validity::LargeHistory)) { /* Marginal is only good enough without tofu. Otherwise we wait for basic trust. */ log_debug ("%s:%s: Discarding marginal signature." "With too little history.", SRCNAME, __func__); continue; } } log_debug ("%s:%s: Classified sender as verified uid validity: %i", SRCNAME, __func__, m_uid.validity()); m_sig = sig; m_is_valid = true; return; } log_debug ("%s:%s: No signature with enough trust. Using first", SRCNAME, __func__); m_sig = m_verify_result.signature(0); return; } bool Mail::is_valid_sig () { return m_is_valid; } void Mail::remove_categories () { const char *decCategory = _("GpgOL: Encrypted Message"); const char *verifyCategory = _("GpgOL: Trusted Sender Address"); remove_category (m_mailitem, decCategory); remove_category (m_mailitem, verifyCategory); } /* Now for some tasty hack: Outlook sometimes does not show the new categories properly but instead does some weird scrollbar thing. This can be avoided by resizing the message a bit. But somehow this only needs to be done once. Weird isn't it? But as this workaround worked let's do it programatically. Fun. Wan't some tomato sauce with this hack? */ static void resize_active_window () { HWND wnd = get_active_hwnd (); static std::vector resized_windows; if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) { /* We only need to do this once per window. XXX But sometimes we also need to do this once per view of the explorer. So for now this might break but we reduce the flicker. A better solution would be to find the current view and track that. */ return; } if (!wnd) { TRACEPOINT; return; } RECT oldpos; if (!GetWindowRect (wnd, &oldpos)) { TRACEPOINT; return; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, /* Anything smaller then 19 was ignored when the window was * maximized on Windows 10 at least with a 1980*1024 * resolution. So I assume it's at least 1 percent. * This is all hackish and ugly but should work for 90%... * hopefully. */ (int)oldpos.right - oldpos.left - 20, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; return; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, (int)oldpos.right - oldpos.left, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; return; } resized_windows.push_back(wnd); } void Mail::update_categories () { const char *decCategory = _("GpgOL: Encrypted Message"); const char *verifyCategory = _("GpgOL: Trusted Sender Address"); if (is_valid_sig()) { add_category (m_mailitem, verifyCategory); } else { remove_category (m_mailitem, verifyCategory); } if (!m_decrypt_result.isNull()) { add_category (m_mailitem, decCategory); } else { /* As a small safeguard against fakes we remove our categories */ remove_category (m_mailitem, decCategory); } resize_active_window(); return; } bool Mail::is_signed() const { return m_verify_result.numSignatures() > 0; } bool Mail::is_encrypted() const { return !m_decrypt_result.isNull(); } int Mail::set_uuid() { char *uuid; if (!m_uuid.empty()) { /* This codepath is reached by decrypt again after a close with discard changes. The close discarded the uuid on the OOM object so we have to set it again. */ log_debug ("%s:%s: Resetting uuid for %p to %s", SRCNAME, __func__, this, m_uuid.c_str()); uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str()); } else { uuid = get_unique_id (m_mailitem, 1, nullptr); log_debug ("%s:%s: uuid for %p set to %s", SRCNAME, __func__, this, uuid); } if (!uuid) { log_debug ("%s:%s: Failed to get/set uuid for %p", SRCNAME, __func__, m_mailitem); return -1; } if (m_uuid.empty()) { m_uuid = uuid; Mail *other = get_mail_for_uuid (uuid); if (other) { /* According to documentation this should not happen as this means that multiple ItemLoad events occured for the same mailobject without unload / destruction of the mail. But it happens. If you invalidate the UI in the selection change event Outlook loads a new mailobject for the mail. Might happen in other surprising cases. We replace in that case as experiments have shown that the last mailobject is the one that is visible. Still troubling state so we log this as an error. */ log_error ("%s:%s: There is another mail for %p " "with uuid: %s replacing it.", SRCNAME, __func__, m_mailitem, uuid); delete other; } s_uid_map.insert (std::pair (m_uuid, this)); log_debug ("%s:%s: uuid for %p is now %s", SRCNAME, __func__, this, m_uuid.c_str()); } xfree (uuid); return 0; } /* Returns 2 if the userid is ultimately trusted. Returns 1 if the userid is fully trusted but has a signature by a key for which we have a secret and which is ultimately trusted. (Direct trust) 0 otherwise */ static int level_4_check (const UserID &uid) { if (uid.isNull()) { return 0; } if (uid.validity () == UserID::Validity::Ultimate) { return 2; } if (uid.validity () == UserID::Validity::Full) { for (const auto sig: uid.signatures ()) { const char *sigID = sig.signerKeyID (); if (sig.isNull() || !sigID) { /* should not happen */ TRACEPOINT; continue; } /* Direct trust information is not available through gnupg so we cached the keys with ultimate trust during parsing and now see if we find a direct trust path.*/ for (const auto secKey: ParseController::get_ultimate_keys ()) { /* Check that the Key id of the key matches */ const char *secKeyID = secKey.keyID (); if (!secKeyID || strcmp (secKeyID, sigID)) { continue; } /* Check that the userID of the signature is the ultimately trusted one. */ const char *sig_uid_str = sig.signerUserID(); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } for (const auto signer_uid: secKey.userIDs ()) { if (signer_uid.validity() != UserID::Validity::Ultimate) { TRACEPOINT; continue; } const char *signer_uid_str = signer_uid.id (); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } if (!strcmp(sig_uid_str, signer_uid_str)) { /* We have a match */ log_debug ("%s:%s: classified %s as ultimate because " "it was signed by uid %s of key %s", SRCNAME, __func__, signer_uid_str, sig_uid_str, secKeyID); return 1; } } } } } return 0; } std::string Mail::get_crypto_summary () { const int level = get_signature_level (); bool enc = is_encrypted (); if (level == 4 && enc) { return _("Security Level 4"); } if (level == 4) { return _("Trust Level 4"); } if (level == 3 && enc) { return _("Security Level 3"); } if (level == 3) { return _("Trust Level 3"); } if (level == 2 && enc) { return _("Security Level 2"); } if (level == 2) { return _("Trust Level 2"); } if (enc) { return _("Encrypted"); } if (is_signed ()) { /* Even if it is signed, if it is not validly signed it's still completly insecure as anyone could have signed this. So we avoid the label "signed" here as this word already implies some security. */ return _("Insecure"); } return _("Insecure"); } std::string Mail::get_crypto_one_line() { bool sig = is_signed (); bool enc = is_encrypted (); if (sig || enc) { if (sig && enc) { return _("Signed and encrypted message"); } else if (sig) { return _("Signed message"); } else if (enc) { return _("Encrypted message"); } } return _("Insecure message"); } std::string Mail::get_crypto_details() { std::string message; /* No signature with keys but error */ if (!is_encrypted() && !is_signed () && m_verify_result.error()) { message = _("You cannot be sure who sent, " "modified and read the message in transit."); message += "\n\n"; message += _("The message was signed but the verification failed with:"); message += "\n"; message += m_verify_result.error().asString(); return message; } /* No crypo, what are we doing here? */ if (!is_encrypted () && !is_signed ()) { return _("You cannot be sure who sent, " "modified and read the message in transit."); } /* Handle encrypt only */ if (is_encrypted() && !is_signed ()) { if (in_de_vs_mode ()) { if (m_sig.isDeVs()) { message += _("The encryption was VS-NfD-compliant."); } else { message += _("The encryption was not VS-NfD-compliant."); } } message += "\n\n"; message += _("You cannot be sure who sent the message because " "it is not signed."); return message; } bool keyFound = true; bool isOpenPGP = m_sig.key().isNull() ? !is_smime() : m_sig.key().protocol() == Protocol::OpenPGP; char *buf; bool hasConflict = false; int level = get_signature_level (); log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i", SRCNAME, __func__, m_sig.validity(), m_sig.summary(), level); if (level == 4) { /* level 4 check for direct trust */ int four_check = level_4_check (m_uid); if (four_check == 2 && m_sig.key().hasSecret ()) { message = _("You signed this message."); } else if (four_check == 1) { message = _("The senders identity was certified by yourself."); } else if (four_check == 2) { message = _("The sender is allowed to certify identities for you."); } else { log_error ("%s:%s:%i BUG: Invalid sigstate.", SRCNAME, __func__, __LINE__); return message; } } else if (level == 3 && isOpenPGP) { /* Level three is only reachable through web of trust and no direct signature. */ message = _("The senders identity was certified by several trusted people."); } else if (level == 3 && !isOpenPGP) { /* Level three is the only level for trusted S/MIME keys. */ gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"), m_sig.key().issuerName()); message = buf; xfree (buf); } else if (level == 2 && m_uid.tofuInfo ().isNull ()) { /* Marginal trust through pgp only */ message = _("Some trusted people " "have certified the senders identity."); } else if (level == 2) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); /* i18n note signcount is always pulral because with signcount 1 we * would not be in this branch. */ gpgrt_asprintf (&buf, _("The senders address is trusted, because " "you have established a communication history " "with this address starting on %s.\n" "You encrypted %i and verified %i messages since."), time, m_uid.tofuInfo().encrCount(), m_uid.tofuInfo().signCount ()); xfree (time); message = buf; xfree (buf); } else if (level == 1) { /* This could be marginal trust through pgp, or tofu with little history. */ if (m_uid.tofuInfo ().signCount() == 1) { message += _("The senders signature was verified for the first time."); } else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because " "you only verified %i messages and encrypted %i messages to " "it since %s."), m_uid.tofuInfo().signCount (), m_uid.tofuInfo().encrCount (), time); xfree (time); message = buf; xfree (buf); } } else { /* Now we are in level 0, this could be a technical problem, no key or just unkown. */ message = is_encrypted () ? _("But the sender address is not trustworthy because:") : _("The sender address is not trustworthy because:"); message += "\n"; keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing); bool general_problem = true; /* First the general stuff. */ if (m_sig.summary() & Signature::Summary::Red) { message += _("The signature is invalid: \n"); } else if (m_sig.summary() & Signature::Summary::SysError || m_verify_result.numSignatures() < 1) { message += _("There was an error verifying the signature.\n"); const auto err = m_sig.status (); if (err) { message += err.asString () + std::string ("\n"); } } else if (m_sig.summary() & Signature::Summary::SigExpired) { message += _("The signature is expired.\n"); } else { message += isOpenPGP ? _("The used key") : _("The used certificate"); message += " "; general_problem = false; } /* Now the key problems */ if ((m_sig.summary() & Signature::Summary::KeyMissing)) { message += _("is not available."); } else if ((m_sig.summary() & Signature::Summary::KeyRevoked)) { message += _("is revoked."); } else if ((m_sig.summary() & Signature::Summary::KeyExpired)) { message += _("is expired."); } else if ((m_sig.summary() & Signature::Summary::BadPolicy)) { message += _("is not meant for signing."); } else if ((m_sig.summary() & Signature::Summary::CrlMissing)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::CrlTooOld)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::TofuConflict) || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { message += _("is not the same as the key that was used " "for this address in the past."); hasConflict = true; } else if (m_uid.isNull()) { gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."), get_sender().c_str()); message += buf; xfree (buf); } else if (((m_sig.validity() & Signature::Validity::Undefined) || (m_sig.validity() & Signature::Validity::Unknown) || (m_sig.summary() == Signature::Summary::None) || (m_sig.validity() == 0))&& !general_problem) { /* Bit of a catch all for weird results. */ if (isOpenPGP) { message += _("is not certified by any trustworthy key."); } else { message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."); } } else if (m_uid.isRevoked()) { message += _("The sender marked this address as revoked."); } else if ((m_sig.validity() & Signature::Validity::Never)) { message += _("is marked as not trustworthy."); } } message += "\n\n"; if (in_de_vs_mode ()) { if (is_signed ()) { if (m_sig.isDeVs ()) { message += _("The signature is VS-NfD-compliant."); } else { message += _("The signature is not VS-NfD-compliant."); } message += "\n"; } if (is_encrypted ()) { if (m_decrypt_result.isDeVs ()) { message += _("The encryption is VS-NfD-compliant."); } else { message += _("The encryption is not VS-NfD-compliant."); } message += "\n\n"; } else { message += "\n"; } } if (hasConflict) { message += _("Click here to change the key used for this address."); } else if (keyFound) { message += isOpenPGP ? _("Click here for details about the key.") : _("Click here for details about the certificate."); } else { message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") : _("Click here to search the certificate on the configured X509 keyserver."); } return message; } int Mail::get_signature_level () const { if (!m_is_signed) { return 0; } if (m_uid.isNull ()) { /* No m_uid matches our sender. */ return 0; } if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate || (m_uid.validity () == UserID::Validity::Full && level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs())) { return 4; } if (m_is_valid && m_uid.validity () == UserID::Validity::Full && (!in_de_vs_mode () || m_sig.isDeVs())) { return 3; } if (m_is_valid) { return 2; } if (m_sig.validity() == Signature::Validity::Marginal) { return 1; } if (m_sig.summary() & Signature::Summary::TofuConflict || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { return 0; } return 0; } int Mail::get_crypto_icon_id () const { int level = get_signature_level (); int offset = is_encrypted () ? ENCRYPT_ICON_OFFSET : 0; return IDI_LEVEL_0 + level + offset; } const char* Mail::get_sig_fpr() const { if (!m_is_signed || m_sig.isNull()) { return nullptr; } return m_sig.fingerprint(); } /** Try to locate the keys for all recipients */ void Mail::locate_keys() { static bool locate_in_progress; if (locate_in_progress) { /** XXX The strangest thing seems to happen here: In get_recipients the lookup for "AddressEntry" on an unresolved address might cause network traffic. So Outlook somehow "detaches" this call and keeps processing window messages while the call is running. So our do_delayed_locate might trigger a second locate. If we access the OOM in this call while we access the same object in the blocked "detached" call we crash. (T3931) After the window message is handled outlook retunrs in the original lookup. A better fix here might be a non recursive lock of the OOM. But I expect that if we lock the handling of the Windowmessage we might deadlock. */ log_debug ("%s:%s: Locate for %p already in progress.", SRCNAME, __func__, this); return; } locate_in_progress = true; // First update oom data to have recipients and sender updated. update_oom_data (); char ** recipients = take_cached_recipients (); KeyCache::instance()->startLocateSecret (get_sender ().c_str ()); KeyCache::instance()->startLocate (get_sender ().c_str ()); KeyCache::instance()->startLocate (recipients); release_cArray (recipients); locate_in_progress = false; } bool Mail::is_html_alternative () const { return m_is_html_alternative; } char * Mail::take_cached_html_body () { char *ret = m_cached_html_body; m_cached_html_body = nullptr; return ret; } char * Mail::take_cached_plain_body () { char *ret = m_cached_plain_body; m_cached_plain_body = nullptr; return ret; } int Mail::get_crypto_flags () const { return m_crypto_flags; } void Mail::set_needs_encrypt (bool value) { m_needs_encrypt = value; } bool Mail::needs_encrypt() const { return m_needs_encrypt; } char ** Mail::take_cached_recipients() { char **ret = m_cached_recipients; m_cached_recipients = nullptr; return ret; } void Mail::append_to_inline_body (const std::string &data) { m_inline_body += data; } int Mail::inline_body_to_body() { if (!m_crypter) { log_error ("%s:%s: No crypter.", SRCNAME, __func__); return -1; } const auto body = m_crypter->get_inline_data (); if (body.empty()) { return 0; } + /* For inline we always work with UTF-8 */ + put_oom_int (m_mailitem, "InternetCodepage", 65001); + int ret = put_oom_string (m_mailitem, "Body", body.c_str ()); return ret; } void Mail::update_crypt_mapi() { log_debug ("%s:%s: Update crypt mapi", SRCNAME, __func__); if (m_crypt_state != NeedsUpdateInMAPI) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); return; } if (!m_crypter) { if (!m_mime_data.empty()) { log_debug ("%s:%s: Have override mime data creating dummy crypter", SRCNAME, __func__); m_crypter = std::shared_ptr (new CryptController (this, false, false, GpgME::UnknownProtocol)); } else { log_error ("%s:%s: No crypter.", SRCNAME, __func__); m_crypt_state = NoCryptMail; return; } } if (m_crypter->update_mail_mapi ()) { log_error ("%s:%s: Failed to update MAPI after crypt", SRCNAME, __func__); m_crypt_state = NoCryptMail; } else { m_crypt_state = WantsSendMIME; } /** If sync we need the crypter in update_crypt_oom */ if (!async_crypt_disabled ()) { // We don't need the crypter anymore. reset_crypter (); } } /** Checks in OOM if the body is either empty or contains the -----BEGIN tag. pair.first -> true if body starts with -----BEGIN pair.second -> true if body is empty. */ static std::pair has_crypt_or_empty_body_oom (Mail *mail) { auto body = mail->get_body(); std::pair ret; ret.first = false; ret.second = false; ltrim (body); if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10)) { ret.first = true; return ret; } if (!body.size()) { ret.second = true; } else { log_mime_parser ("%s:%s: Body found in %p : \"%s\"", SRCNAME, __func__, mail, body.c_str ()); } return ret; } void Mail::update_crypt_oom() { log_debug ("%s:%s: Update crypt oom for %p", SRCNAME, __func__, this); if (m_crypt_state != NeedsUpdateInOOM) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); reset_crypter (); return; } if (do_pgp_inline ()) { if (inline_body_to_body ()) { log_error ("%s:%s: Inline body to body failed %p.", SRCNAME, __func__, this); gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY); m_crypt_state = NoCryptMail; return; } } if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ()) { /* We put the PIDNameContentType headers here for exchange because this is the only way we found to inject the smime-type. */ if (put_pa_string (m_mailitem, PR_PIDNameContentType_DASL, "application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m")) { log_debug ("%s:%s: Failed to put PIDNameContentType for %p.", SRCNAME, __func__, this); } } /** When doing async update_crypt_mapi follows and needs the crypter. */ if (async_crypt_disabled ()) { reset_crypter (); } const auto pair = has_crypt_or_empty_body_oom (this); if (pair.first) { log_debug ("%s:%s: Looks like inline body. You can pass %p.", SRCNAME, __func__, this); m_crypt_state = WantsSendInline; return; } // We are in MIME land. Wipe the body. if (wipe (true)) { log_debug ("%s:%s: Cancel send for %p.", SRCNAME, __func__, this); wchar_t *title = utf8_to_wchar (_("GpgOL: Encryption not possible!")); wchar_t *msg = utf8_to_wchar (_( "Outlook returned an error when trying to send the encrypted mail.\n\n" "Please restart Outlook and try again.\n\n" "If it still fails consider using an encrypted attachment or\n" "switching to PGP/Inline in GpgOL's options.")); MessageBoxW (get_active_hwnd(), msg, title, MB_ICONERROR | MB_OK); xfree (msg); xfree (title); m_crypt_state = NoCryptMail; return; } m_crypt_state = NeedsSecondAfterWrite; return; } void Mail::set_window_enabled (bool value) { if (!value) { m_window = get_active_hwnd (); } log_debug ("%s:%s: enable window %p %i", SRCNAME, __func__, m_window, value); EnableWindow (m_window, value ? TRUE : FALSE); } bool Mail::check_inline_response () { /* Async sending might lead to crashes when the send invocation is done. * For now we treat every mail as an inline response to disable async * encryption. :-( For more details see: T3838 */ #ifdef DO_ASYNC_CRYPTO m_async_crypt_disabled = false; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return false; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; return false; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { return false; } // We have inline response // Check if we are it. It's a bit naive but meh. Worst case // is that we think inline response too often and do sync // crypt where we could do async crypt. char * inlineSubject = get_oom_string (inlineResponse, "Subject"); gpgol_release (inlineResponse); const auto subject = get_subject (); if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject)) { log_debug ("%s:%s: Detected inline response for '%p'", SRCNAME, __func__, this); m_async_crypt_disabled = true; } xfree (inlineSubject); #else m_async_crypt_disabled = true; #endif return m_async_crypt_disabled; } // static Mail * Mail::get_last_mail () { if (!s_last_mail || !is_valid_ptr (s_last_mail)) { s_last_mail = nullptr; } return s_last_mail; } // static void Mail::invalidate_last_mail () { s_last_mail = nullptr; } // static void Mail::locate_all_crypto_recipients() { if (!opt.autoresolve) { return; } std::map::iterator it; for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it) { if (it->second->needs_crypto ()) { it->second->locate_keys (); } } } int Mail::remove_our_attachments () { LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { TRACEPOINT; return 0; } int count = get_oom_int (attachments, "Count"); LPDISPATCH to_delete[count]; int del_cnt = 0; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str()); if (!attachment) { TRACEPOINT; continue; } attachtype_t att_type; if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type)) { /* Not our attachment. */ gpgol_release (attachment); continue; } if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS || att_type == ATTACHTYPE_MOSSTEMPL) { /* One of ours to delete. */ to_delete[del_cnt++] = attachment; /* Dont' release yet */ continue; } gpgol_release (attachment); } gpgol_release (attachments); int ret = 0; for (int i = 0; i < del_cnt; i++) { LPDISPATCH attachment = to_delete[i]; /* Delete the attachments that are marked to delete */ if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Error: deleting attachment %i", SRCNAME, __func__, i); ret = -1; } gpgol_release (attachment); } return ret; } /* We are very verbose because if we fail it might mean that we have leaked plaintext -> critical. */ bool Mail::has_crypted_or_empty_body () { const auto pair = has_crypt_or_empty_body_oom (this); if (pair.first /* encrypted marker */) { log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.", SRCNAME, __func__, this); return true; } if (!pair.second) { log_debug ("%s:%s: Unexpected content detected. Return false %p.", SRCNAME, __func__, this); return false; } // Pair second == true (is empty) can happen on OOM error. LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message && pair.second) { if (message) { gpgol_release (message); } return true; } size_t r_nbytes = 0; char *mapi_body = mapi_get_body (message, &r_nbytes); gpgol_release (message); if (!mapi_body || !r_nbytes) { // Body or bytes are null. we are empty. xfree (mapi_body); log_debug ("%s:%s: MAPI error or empty message. Return true. %p.", SRCNAME, __func__, this); return true; } if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10)) { // Body is crypt. log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.", SRCNAME, __func__, this); xfree (mapi_body); return true; } xfree (mapi_body); log_debug ("%s:%s: Found mapi body. Return false. %p.", SRCNAME, __func__, this); return false; } std::string Mail::get_verification_result_dump() { std::stringstream ss; ss << m_verify_result; return ss.str(); } void Mail::set_block_status() { SPropValue prop; LPMESSAGE message = get_oom_base_message (m_mailitem); prop.ulPropTag = PR_BLOCK_STATUS; prop.Value.l = 1; HRESULT hr = message->SetProps (1, &prop, NULL); if (hr) { log_error ("%s:%s: can't set block value: hr=%#lx\n", SRCNAME, __func__, hr); } gpgol_release (message); return; } void Mail::set_block_html(bool value) { m_block_html = value; } diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp index 1dcef01..a4774cb 100644 --- a/src/mapihelp.cpp +++ b/src/mapihelp.cpp @@ -1,3844 +1,3844 @@ /* mapihelp.cpp - Helper functions for MAPI * Copyright (C) 2005, 2007, 2008 g10 Code 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 "mymapi.h" #include "mymapitags.h" #include "common.h" #include "rfc822parse.h" #include "mapihelp.h" #include "parsetlv.h" #include "oomhelp.h" #include #ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA #define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011 #endif #ifndef CRYPT_E_ASN1_BADTAG #define CRYPT_E_ASN1_BADTAG 0x8009310B #endif static int get_attach_method (LPATTACH obj); static int has_smime_filename (LPATTACH obj); static char *get_attach_mime_tag (LPATTACH obj); /* Print a MAPI property to the log stream. */ void log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname) { HRESULT hr; LPSPropValue propval = NULL; size_t keylen; void *key; char *buf; if (!message) return; /* No message: Nop. */ hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval); if (FAILED (hr)) { log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n", SRCNAME, __func__, propname, hr); return; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_BINARY: keylen = propval->Value.bin.cb; key = propval->Value.bin.lpb; log_hexdump (key, keylen, "%s: %20s=", __func__, propname); break; case PT_UNICODE: buf = wchar_to_utf8 (propval->Value.lpszW); if (!buf) log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__); else log_debug ("%s: %20s=`%s'", __func__, propname, buf); xfree (buf); break; case PT_STRING8: log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA); break; case PT_LONG: log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l); break; default: log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n", SRCNAME, __func__, propname, PROP_TYPE (propval->ulPropTag) ); return; } MAPIFreeBuffer (propval); } /* Helper to create a named property. */ static ULONG create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func) { HRESULT hr; LPSPropTagArray proparr = NULL; MAPINAMEID mnid, *pmnid; wchar_t *propname = wcsdup (name); /* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties. */ GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06, 0x1b, 0x03, 0x10, 0x04}}; ULONG result; memset (&mnid, 0, sizeof mnid); mnid.lpguid = &guid; mnid.ulKind = MNID_STRING; mnid.Kind.lpwstrName = propname; pmnid = &mnid; hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr); xfree (propname); if (FAILED (hr)) proparr = NULL; if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) { log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n", SRCNAME, func, hr); result = 0; } else result = (proparr->aulPropTag[0] & 0xFFFF0000); if (proparr) MAPIFreeBuffer (proparr); return result; } /* Return the property tag for GpgOL Msg Class. */ int get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the property tag for GpgOL Old Msg Class. The Old Msg Class saves the message class as seen before we changed it the first time. */ int get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Old Msg Class", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the property tag for GpgOL Attach Type. */ int get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__))) return -1; *r_tag |= PT_LONG; return 0; } /* Return the property tag for GpgOL Sig Status. */ int get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the property tag for GpgOL Protect IV. */ int get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__))) return -1; *r_tag |= PT_BINARY; return 0; } /* Return the property tag for GpgOL Last Decrypted. */ int get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__))) return -1; *r_tag |= PT_BINARY; return 0; } /* Return the property tag for GpgOL MIME structure. */ int get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the property tag for GpgOL Charset. */ int get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the property tag for GpgOL Draft Info. */ int get_gpgoldraftinfo_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Draft Info", __func__))) return -1; *r_tag |= PT_STRING8; return 0; } /* Return the tag of the Internet Charset Body property which seems to hold the PR_BODY as received and thus before charset conversion. */ int get_internetcharsetbody_tag (LPMESSAGE message, ULONG *r_tag) { HRESULT hr; LPSPropTagArray proparr = NULL; MAPINAMEID mnid, *pmnid; /* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */ GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0, 0x4F, 0xD6, 0x56, 0x85}}; wchar_t propname[] = L"Internet Charset Body"; int result; memset (&mnid, 0, sizeof mnid); mnid.lpguid = &guid; mnid.ulKind = MNID_STRING; mnid.Kind.lpwstrName = propname; pmnid = &mnid; hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr); if (FAILED (hr)) proparr = NULL; if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) { log_debug ("%s:%s: can't get the Internet Charset Body property:" " hr=%#lx\n", SRCNAME, __func__, hr); result = -1; } else { result = 0; *r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY); } if (proparr) MAPIFreeBuffer (proparr); return result; } /* Return the property tag for GpgOL UUID Info. */ static int get_gpgoluid_tag (LPMESSAGE message, ULONG *r_tag) { if (!(*r_tag = create_gpgol_tag (message, L"GpgOL UID", __func__))) return -1; *r_tag |= PT_UNICODE; return 0; } char * mapi_get_uid (LPMESSAGE msg) { /* If the UUID is not in OOM maybe we find it in mapi. */ if (!msg) { log_error ("%s:%s: Called without message", SRCNAME, __func__); return NULL; } ULONG tag; if (get_gpgoluid_tag (msg, &tag)) { log_debug ("%s:%s: Failed to get tag for '%p'", SRCNAME, __func__, msg); return NULL; } LPSPropValue propval = NULL; HRESULT hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (hr) { log_debug ("%s:%s: Failed to get prop for '%p'", SRCNAME, __func__, msg); return NULL; } char *ret = NULL; if (PROP_TYPE (propval->ulPropTag) == PT_UNICODE) { ret = wchar_to_utf8 (propval->Value.lpszW); log_debug ("%s:%s: Fund uuid in MAPI for %p", SRCNAME, __func__, msg); } else if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) { ret = strdup (propval->Value.lpszA); log_debug ("%s:%s: Fund uuid in MAPI for %p", SRCNAME, __func__, msg); } MAPIFreeBuffer (propval); return ret; } /* A Wrapper around the SaveChanges method. This function should be called indirect through the mapi_save_changes macro. Returns 0 on success. */ int mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body, const char *dbg_file, const char *dbg_func) { HRESULT hr; SPropTagArray proparray; int any = 0; if (mapi_has_last_decrypted (message)) { 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", log_srcname (dbg_file), dbg_func); else any = 1; 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", log_srcname (dbg_file), dbg_func); else any = 1; } if (!only_del_body || any) { int i; for (i = 0, hr = 0; hr && i < 10; i++) { hr = message->SaveChanges (flags); if (hr) { log_debug ("%s:%s: Failed try to save.", SRCNAME, __func__); Sleep (1000); } } if (hr) { log_error ("%s:%s: SaveChanges(%lu) failed: hr=%#lx\n", log_srcname (dbg_file), dbg_func, (unsigned long)flags, hr); return -1; } } return 0; } /* Set an arbitary header in the message MSG with NAME to the value VAL. */ int mapi_set_header (LPMESSAGE msg, const char *name, const char *val) { HRESULT hr; LPSPropTagArray pProps = NULL; SPropValue pv; MAPINAMEID mnid, *pmnid; /* {00020386-0000-0000-C000-000000000046} -> GUID For X-Headers */ GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} }; int result; if (!msg) return -1; memset (&mnid, 0, sizeof mnid); mnid.lpguid = &guid; mnid.ulKind = MNID_STRING; mnid.Kind.lpwstrName = utf8_to_wchar (name); pmnid = &mnid; hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps); xfree (mnid.Kind.lpwstrName); if (FAILED (hr)) { pProps = NULL; log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n", SRCNAME, __func__, name, hr); result = -1; } else { pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8; pv.Value.lpszA = (char *)val; hr = HrSetOneProp(msg, &pv); if (hr) { log_error ("%s:%s: can't set header `%s': hr=%#lx\n", SRCNAME, __func__, name, hr); result = -1; } else result = 0; } if (pProps) MAPIFreeBuffer (pProps); return result; } /* Return the headers as ASCII string. Returns empty string on failure. */ std::string mapi_get_header (LPMESSAGE message) { HRESULT hr; LPSTREAM stream; ULONG bRead; std::string ret; if (!message) return ret; hr = message->OpenProperty (PR_TRANSPORT_MESSAGE_HEADERS_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); return ret; } char buf[8192]; while ((hr = stream->Read (buf, 8192, &bRead)) == S_OK || hr == S_FALSE) { if (!bRead) { // EOF break; } ret += std::string (buf, bRead); } gpgol_release (stream); return ret; } /* Return the body as a new IStream object. Returns NULL on failure. The stream returns the body as an ASCII stream (Use mapi_get_body for an UTF-8 value). */ LPSTREAM mapi_get_body_as_stream (LPMESSAGE message) { HRESULT hr; ULONG tag; LPSTREAM stream; if (!message) return NULL; if (!get_internetcharsetbody_tag (message, &tag) ) { /* The store knows about the Internet Charset Body property, thus try to get the body from this property if it exists. */ - + hr = message->OpenProperty (tag, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (!hr) return stream; log_debug ("%s:%s: OpenProperty tag=%lx failed: hr=%#lx", SRCNAME, __func__, tag, hr); } /* We try to get it as an ASCII body. If this fails we would either need to implement some kind of stream filter to translated to utf-8 or read everyting into a memory buffer and [provide an istream from that memory buffer. */ hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); return NULL; } return stream; } /* Return the body of the message in an allocated buffer. The buffer is guaranteed to be Nul terminated. The actual length (ie. the strlen()) will be stored at R_NBYTES. The body will be returned in UTF-8 encoding. Returns NULL if no body is available. */ char * mapi_get_body (LPMESSAGE message, size_t *r_nbytes) { HRESULT hr; LPSPropValue lpspvFEID = NULL; LPSTREAM stream; STATSTG statInfo; ULONG nread; char *body = NULL; if (r_nbytes) *r_nbytes = 0; hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID); if (SUCCEEDED (hr)) /* Message is small enough to be retrieved directly. */ { switch ( PROP_TYPE (lpspvFEID->ulPropTag) ) { case PT_UNICODE: body = wchar_to_utf8 (lpspvFEID->Value.lpszW); if (!body) log_debug ("%s: error converting to utf8\n", __func__); break; case PT_STRING8: body = xstrdup (lpspvFEID->Value.lpszA); break; default: log_debug ("%s: proptag=0x%08lx not supported\n", __func__, lpspvFEID->ulPropTag); break; } MAPIFreeBuffer (lpspvFEID); } else /* Message is large; use an IStream to read it. */ { hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); return NULL; } hr = stream->Stat (&statInfo, STATFLAG_NONAME); if (hr) { log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); return NULL; } /* Fixme: We might want to read only the first 1k to decide whether this is actually an OpenPGP message and only then continue reading. */ body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2); hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread); if (hr) { log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (body); gpgol_release (stream); return NULL; } body[nread] = 0; body[nread+1] = 0; if (nread != statInfo.cbSize.QuadPart) { log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (body); gpgol_release (stream); return NULL; } gpgol_release (stream); { char *tmp; tmp = wchar_to_utf8 ((wchar_t*)body); if (!tmp) log_debug ("%s: error converting to utf8\n", __func__); else { xfree (body); body = tmp; } } } if (r_nbytes) *r_nbytes = strlen (body); return body; } /* Look at the body of the MESSAGE and try to figure out whether this is a supported PGP message. Returns the new message class or NULL if it does not look like a PGP message. If r_nobody is not null it is set to true if no body was found. */ static char * get_msgcls_from_pgp_lines (LPMESSAGE message, bool *r_nobody = nullptr) { HRESULT hr; LPSTREAM stream; STATSTG statInfo; ULONG nread; size_t nbytes; char *body = NULL; char *p; char *msgcls = NULL; int is_wchar = 0; if (r_nobody) { *r_nobody = false; } stream = mapi_get_body_as_stream (message); if (!stream) { log_debug ("%s:%s: Failed to get body ASCII stream.", SRCNAME, __func__); hr = message->OpenProperty (PR_BODY_W, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_error ("%s:%s: Failed to get w_body stream. : hr=%#lx", SRCNAME, __func__, hr); if (r_nobody) { *r_nobody = true; } return NULL; } else { is_wchar = 1; } } hr = stream->Stat (&statInfo, STATFLAG_NONAME); if (hr) { log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); return NULL; } /* We read only the first 1k to decide whether this is actually an OpenPGP armored message . */ nbytes = (size_t)statInfo.cbSize.QuadPart; if (nbytes > 1024*2) nbytes = 1024*2; body = (char*)xmalloc (nbytes + 2); hr = stream->Read (body, nbytes, &nread); if (hr) { log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (body); gpgol_release (stream); return NULL; } body[nread] = 0; body[nread+1] = 0; if (nread != nbytes) { log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (body); gpgol_release (stream); return NULL; } gpgol_release (stream); if (is_wchar) { char *tmp; tmp = wchar_to_utf8 ((wchar_t*)body); if (!tmp) log_debug ("%s: error converting to utf8\n", __func__); else { xfree (body); body = tmp; } } /* The first ~1k of the body of the message is now available in the utf-8 string BODY. Walk over it to figure out its type. */ for (p=body; p && *p; p = ((p=strchr (p+1, '\n')) ? (p+1) : NULL)) { if (!strncmp (p, "-----BEGIN PGP ", 15)) { /* Enabling clearsigned detection for Outlook 2010 and later would result in data loss as the signature is not reverted. */ if (!strncmp (p+15, "SIGNED MESSAGE-----", 19) && trailing_ws_p (p+15+19)) msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned"); else if (!strncmp (p+15, "MESSAGE-----", 12) && trailing_ws_p (p+15+12)) msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage"); break; } else if (!trailing_ws_p (p)) { /* We have text before the message. In that case we need to break because some bad MUA's like Outlook do not insert quote characters before a replied to message. In that case the reply to an inline Mail from an Outlook without GpgOL enabled could cause the behavior that we would detect the original message. */ log_debug ("%s:%s: Detected non whitespace %c before a PGP Marker", SRCNAME, __func__, *p); break; } } xfree (body); return msgcls; } /* Check whether the message is really a CMS encrypted message. We check here whether the message is really encrypted by looking at the object identifier inside the CMS data. Returns: -1 := Unknown message type, 0 := The message is signed, 1 := The message is encrypted. This function is required for two reasons: 1. Due to a bug in CryptoEx which sometimes assignes the *.CexEnc message class to signed messages and only updates the message class after accessing them. Thus in old stores there may be a lot of *.CexEnc message which are actually just signed. 2. If the smime-type parameter is missing we need another way to decide whether to decrypt or to verify. 3. Some messages lack a PR_TRANSPORT_MESSAGE_HEADERS and thus it is not possible to deduce the message type from the mail headers. This function may be used to identify the message anyway. */ static int is_really_cms_encrypted (LPMESSAGE message) { HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; unsigned int pos, n_attach; int result = -1; /* Unknown. */ LPATTACH att = NULL; LPSTREAM stream = NULL; char buffer[24]; /* 24 bytes are more than enough to peek at. Cf. ksba_cms_identify() from the libksba package. */ const char *p; ULONG nread; size_t n; tlvinfo_t ti; hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); return -1; } hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); return -1; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (n_attach != 1) { FreeProws (mapirows); gpgol_release (mapitable); log_debug ("%s:%s: not just one attachment", SRCNAME, __func__); return -1; } pos = 0; if (mapirows->aRow[pos].cValues < 1) { log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos); goto leave; } if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM) { log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos); goto leave; } hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx", SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); goto leave; } if (!has_smime_filename (att)) { log_debug ("%s:%s: no smime filename", SRCNAME, __func__); goto leave; } if (get_attach_method (att) != ATTACH_BY_VALUE) { log_debug ("%s:%s: wrong attach method", SRCNAME, __func__); goto leave; } hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN*) &stream); if (FAILED (hr)) { log_error ("%s:%s: can't open data stream of attachment: hr=%#lx", SRCNAME, __func__, hr); goto leave; } hr = stream->Read (buffer, sizeof buffer, &nread); if ( hr != S_OK ) { log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } if (nread < sizeof buffer) { log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); goto leave; } p = buffer; n = nread; if (parse_tlv (&p, &n, &ti)) goto leave; if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE && ti.is_cons) ) goto leave; if (parse_tlv (&p, &n, &ti)) goto leave; if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID && !ti.is_cons && ti.length) || ti.length > n) goto leave; /* Now is this enveloped data (1.2.840.113549.1.7.3) or signed data (1.2.840.113549.1.7.2) ? */ if (ti.length == 9) { if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9)) result = 1; /* Encrypted. */ else if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9)) result = 0; /* Signed. */ } leave: if (stream) gpgol_release (stream); if (att) gpgol_release (att); FreeProws (mapirows); gpgol_release (mapitable); return result; } /* Return the content-type of the first and only attachment of MESSAGE or NULL if it does not exists. Caller must free. */ static char * get_first_attach_mime_tag (LPMESSAGE message) { HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; unsigned int pos, n_attach; LPATTACH att = NULL; char *result = NULL; hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); return NULL; } hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); return NULL; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (n_attach < 1) { FreeProws (mapirows); gpgol_release (mapitable); log_debug ("%s:%s: less then one attachment", SRCNAME, __func__); return NULL; } pos = 0; if (mapirows->aRow[pos].cValues < 1) { log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos); goto leave; } if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM) { log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos); goto leave; } hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx", SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); goto leave; } /* Note: We do not expect a filename. */ if (get_attach_method (att) != ATTACH_BY_VALUE) { log_debug ("%s:%s: wrong attach method", SRCNAME, __func__); goto leave; } result = get_attach_mime_tag (att); leave: if (att) gpgol_release (att); FreeProws (mapirows); gpgol_release (mapitable); return result; } /* Look at the first attachment's content type to determine the messageclass. */ static char * get_msgcls_from_first_attachment (LPMESSAGE message) { char *ret = nullptr; char *attach_mime = get_first_attach_mime_tag (message); if (!attach_mime) { return nullptr; } if (!strcmp (attach_mime, "application/pgp-encrypted")) { ret = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted"); xfree (attach_mime); } else if (!strcmp (attach_mime, "application/pgp-signature")) { ret = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (attach_mime); } return ret; } /* Helper for mapi_change_message_class. Returns the new message class as an allocated string. Most message today are of the message class "IPM.Note". However a PGP/MIME encrypted message also has this class. We need to see whether we can detect such a mail right here and change the message class accordingly. */ static char * change_message_class_ipm_note (LPMESSAGE message) { char *newvalue = NULL; char *ct, *proto; ct = mapi_get_message_content_type (message, &proto, NULL); log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct ? ct : "null"); if (ct && proto) { log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto); if (!strcmp (ct, "multipart/encrypted") && !strcmp (proto, "application/pgp-encrypted")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted"); } else if (!strcmp (ct, "multipart/signed") && !strcmp (proto, "application/pgp-signature")) { /* Sometimes we receive a PGP/MIME signed message with a class IPM.Note. */ newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } xfree (proto); } else if (ct && !strcmp (ct, "application/ms-tnef")) { /* ms-tnef can either be inline PGP or PGP/MIME. First check for inline and then look at the attachments if they look like PGP /MIME .*/ newvalue = get_msgcls_from_pgp_lines (message); if (!newvalue) { /* So no PGP Inline. Lets look at the attachment. */ newvalue = get_msgcls_from_first_attachment (message); } } else if (!ct || !strcmp (ct, "text/plain") || !strcmp (ct, "multipart/mixed") || !strcmp (ct, "multipart/alternative") || !strcmp (ct, "multipart/related") || !strcmp (ct, "text/html")) { bool has_no_body = false; /* It is quite common to have a multipart/mixed or alternative mail with separate encrypted PGP parts. Look at the body to decide. */ newvalue = get_msgcls_from_pgp_lines (message, &has_no_body); if (!newvalue && has_no_body && ct && !strcmp (ct, "multipart/mixed")) { /* This is uncommon. But some Exchanges might break a PGP/MIME mail this way. Let's take a look at the attachments. Maybe it's a PGP/MIME mail. */ log_debug ("%s:%s: Multipart mixed without body found. Looking at attachments.", SRCNAME, __func__); newvalue = get_msgcls_from_first_attachment (message); } } xfree (ct); return newvalue; } /* Helper for mapi_change_message_class. Returns the new message class as an allocated string. This function is used for the message class "IPM.Note.SMIME". It indicates an S/MIME opaque encrypted or signed message. This may also be an PGP/MIME mail. */ static char * change_message_class_ipm_note_smime (LPMESSAGE message) { char *newvalue = NULL; char *ct, *proto, *smtype; ct = mapi_get_message_content_type (message, &proto, &smtype); if (ct) { log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct); if (proto && !strcmp (ct, "multipart/signed") && !strcmp (proto, "application/pgp-signature")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } else if (ct && !strcmp (ct, "application/ms-tnef")) { /* So no PGP Inline. Lets look at the attachment. */ char *attach_mime = get_first_attach_mime_tag (message); if (!attach_mime) { xfree (ct); xfree (proto); return nullptr; } if (!strcmp (attach_mime, "multipart/signed")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (attach_mime); } } else if (!opt.enable_smime) ; /* S/MIME not enabled; thus no further checks. */ else if (smtype) { log_debug ("%s:%s: smime-type is '%s'", SRCNAME, __func__, smtype); if (!strcmp (ct, "application/pkcs7-mime") || !strcmp (ct, "application/x-pkcs7-mime")) { if (!strcmp (smtype, "signed-data")) newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); else if (!strcmp (smtype, "enveloped-data")) newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); } } else { /* No smime type. The filename parameter is often not reliable, thus we better look into the message to see if it is encrypted and assume an opaque signed one if this is not the case. */ switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; } } xfree (smtype); xfree (proto); xfree (ct); } else { log_debug ("%s:%s: message has no content type", SRCNAME, __func__); /* CryptoEx (or the Toltec Connector) create messages without the transport headers property and thus we don't know the content type. We try to detect the message type anyway by looking into the first and only attachments. */ switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; default: /* Unknown. */ break; } } /* If we did not found anything but let's change the class anyway. */ if (!newvalue && opt.enable_smime) newvalue = xstrdup ("IPM.Note.GpgOL"); return newvalue; } /* Helper for mapi_change_message_class. Returns the new message class as an allocated string. This function is used for the message class "IPM.Note.SMIME.MultipartSigned". This is an S/MIME message class but smime support is not enabled. We need to check whether this is actually a PGP/MIME message. */ static char * change_message_class_ipm_note_smime_multipartsigned (LPMESSAGE message) { char *newvalue = NULL; char *ct, *proto; ct = mapi_get_message_content_type (message, &proto, NULL); if (ct) { log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct); if (proto && !strcmp (ct, "multipart/signed") && !strcmp (proto, "application/pgp-signature")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } else if (!strcmp (ct, "wks.confirmation.mail")) { newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation"); } else if (ct && !strcmp (ct, "application/ms-tnef")) { /* So no PGP Inline. Lets look at the attachment. */ char *attach_mime = get_first_attach_mime_tag (message); if (!attach_mime) { xfree (ct); xfree (proto); return nullptr; } if (!strcmp (attach_mime, "multipart/signed")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (attach_mime); } } xfree (proto); xfree (ct); } else log_debug ("%s:%s: message has no content type", SRCNAME, __func__); return newvalue; } /* Helper for mapi_change_message_class. Returns the new message class as an allocated string. This function is used for the message classes "IPM.Note.Secure.CexSig" and "IPM.Note.Secure.Cexenc" (in the latter case IS_CEXSIG is true). These are CryptoEx generated signature or encryption messages. */ static char * change_message_class_ipm_note_secure_cex (LPMESSAGE message, int is_cexenc) { char *newvalue = NULL; char *ct, *smtype, *proto; ct = mapi_get_message_content_type (message, &proto, &smtype); if (ct) { log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct); if (smtype) log_debug ("%s:%s: smime-type is '%s'", SRCNAME, __func__, smtype); if (proto) log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto); if (smtype) { if (!strcmp (ct, "application/pkcs7-mime") || !strcmp (ct, "application/x-pkcs7-mime")) { if (!strcmp (smtype, "signed-data")) newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); else if (!strcmp (smtype, "enveloped-data")) newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); } } if (!newvalue && proto) { if (!strcmp (ct, "multipart/signed") && (!strcmp (proto, "application/pkcs7-signature") || !strcmp (proto, "application/x-pkcs7-signature"))) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } else if (!strcmp (ct, "multipart/signed") && (!strcmp (proto, "application/pgp-signature"))) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } } if (!newvalue && (!strcmp (ct, "text/plain") || !strcmp (ct, "multipart/alternative") || !strcmp (ct, "multipart/mixed"))) { newvalue = get_msgcls_from_pgp_lines (message); } if (!newvalue) { switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; } } xfree (smtype); xfree (proto); xfree (ct); } else { log_debug ("%s:%s: message has no content type", SRCNAME, __func__); if (is_cexenc) { switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; } } else { char *mimetag; mimetag = get_first_attach_mime_tag (message); if (mimetag && !strcmp (mimetag, "multipart/signed")) newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (mimetag); } if (!newvalue) { newvalue = get_msgcls_from_pgp_lines (message); } } if (!newvalue) newvalue = xstrdup ("IPM.Note.GpgOL"); return newvalue; } static msgtype_t string_to_type (const char *s) { if (!s || strlen (s) < 14) { return MSGTYPE_UNKNOWN; } if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.')) { s += 14; if (!*s) return MSGTYPE_GPGOL; else if (!strcmp (s, ".MultipartSigned")) return MSGTYPE_GPGOL_MULTIPART_SIGNED; else if (!strcmp (s, ".MultipartEncrypted")) return MSGTYPE_GPGOL_MULTIPART_ENCRYPTED; else if (!strcmp (s, ".OpaqueSigned")) return MSGTYPE_GPGOL_OPAQUE_SIGNED; else if (!strcmp (s, ".OpaqueEncrypted")) return MSGTYPE_GPGOL_OPAQUE_ENCRYPTED; else if (!strcmp (s, ".ClearSigned")) return MSGTYPE_GPGOL_CLEAR_SIGNED; else if (!strcmp (s, ".PGPMessage")) return MSGTYPE_GPGOL_PGP_MESSAGE; else if (!strcmp (s, ".WKSConfirmation")) return MSGTYPE_GPGOL_WKS_CONFIRMATION; else log_debug ("%s:%s: message class `%s' not supported", SRCNAME, __func__, s-14); } else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.')) return MSGTYPE_SMIME; return MSGTYPE_UNKNOWN; } /* This function checks whether MESSAGE requires processing by us and adjusts the message class to our own. By passing true for SYNC_OVERRIDE the actual MAPI message class will be updated to our own message class overide. Return true if the message was changed. */ int mapi_change_message_class (LPMESSAGE message, int sync_override, msgtype_t *r_type) { HRESULT hr; ULONG tag; SPropValue prop; LPSPropValue propval = NULL; char *newvalue = NULL; int need_save = 0; int have_override = 0; if (!message) return 0; /* No message: Nop. */ if (get_gpgolmsgclass_tag (message, &tag) ) return 0; /* Ooops. */ hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) { hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n", SRCNAME, __func__, hr); return 0; } } else { have_override = 1; log_debug ("%s:%s: have override message class\n", SRCNAME, __func__); } if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) { const char *s = propval->Value.lpszA; int cexenc = 0; log_debug ("%s:%s: checking message class `%s'", SRCNAME, __func__, s); if (!strcmp (s, "IPM.Note")) { newvalue = change_message_class_ipm_note (message); } else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME")) { newvalue = change_message_class_ipm_note_smime (message); } else if (opt.enable_smime && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.')) { /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional but the previous condition has already taken care of this). Note that we can't just insert a new part and keep the SMIME; we need to change the SMIME part of the class name so that Outlook does not process it as an SMIME message. */ char *tmp = change_message_class_ipm_note_smime_multipartsigned (message); /* This case happens even for PGP/MIME mails but that is ok as we later fiddle out the protocol. But we have to check if this is a WKS Mail now so that we can do the special handling for that. */ if (tmp && !strcmp (tmp, "IPM.Note.GpgOL.WKSConfirmation")) { newvalue = tmp; } else { xfree (tmp); newvalue = (char*)xmalloc (strlen (s) + 1); strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14); } } else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned")) { /* This is an S/MIME message class but smime support is not enabled. We need to check whether this is actually a PGP/MIME message. */ newvalue = change_message_class_ipm_note_smime_multipartsigned (message); } else if (sync_override && have_override && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.')) { /* In case the original message class is not yet an GpgOL class we set it here. This is needed to convince Outlook not to do any special processing for IPM.Note.SMIME etc. */ LPSPropValue propval2 = NULL; hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval2); if (!SUCCEEDED (hr)) { log_debug ("%s:%s: Failed to get PR_MESSAGE_CLASS_A property.", SRCNAME, __func__); } else if (PROP_TYPE (propval2->ulPropTag) != PT_STRING8) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is not string.", SRCNAME, __func__); } else if (!propval2->Value.lpszA) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is null.", SRCNAME, __func__); } else if (!strcmp (propval2->Value.lpszA, s)) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is already the same.", SRCNAME, __func__); } else { newvalue = (char*)xstrdup (s); } MAPIFreeBuffer (propval2); } else if (opt.enable_smime && (!strcmp (s, "IPM.Note.Secure.CexSig") || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc")))) { newvalue = change_message_class_ipm_note_secure_cex (message, cexenc); } if (r_type && !newvalue) { *r_type = string_to_type (s); } } if (!newvalue) { /* We use our Sig-Status property to mark messages which passed this function. This helps us to avoid later tests. */ if (!mapi_has_sig_status (message)) { mapi_set_sig_status (message, "#"); need_save = 1; } } else { if (r_type) { *r_type = string_to_type (newvalue); } /* Save old message class if not yet done. (The second condition is just a failsafe check). */ if (!get_gpgololdmsgclass_tag (message, &tag) && PROP_TYPE (propval->ulPropTag) == PT_STRING8) { LPSPropValue propval2 = NULL; hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2); if (!FAILED (hr)) MAPIFreeBuffer (propval2); else { /* No such property - save it. */ log_debug ("%s:%s: saving old message class\n", SRCNAME, __func__); prop.ulPropTag = tag; prop.Value.lpszA = propval->Value.lpszA; hr = message->SetProps (1, &prop, NULL); if (hr) { log_error ("%s:%s: can't save old message class: hr=%#lx\n", SRCNAME, __func__, hr); MAPIFreeBuffer (propval); return 0; } need_save = 1; } } /* Change message class. */ log_debug ("%s:%s: setting message class to `%s'\n", SRCNAME, __func__, newvalue); prop.ulPropTag = PR_MESSAGE_CLASS_A; prop.Value.lpszA = newvalue; hr = message->SetProps (1, &prop, NULL); xfree (newvalue); if (hr) { log_error ("%s:%s: can't set message class: hr=%#lx\n", SRCNAME, __func__, hr); MAPIFreeBuffer (propval); return 0; } need_save = 1; } MAPIFreeBuffer (propval); if (need_save) { if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE)) return 0; } return 1; } /* Return the message class. This function will never return NULL so it is mostly useful for debugging. Caller needs to release the returned string. */ char * mapi_get_message_class (LPMESSAGE message) { HRESULT hr; LPSPropValue propval = NULL; char *retstr; if (!message) return xstrdup ("[No message]"); hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n", SRCNAME, __func__, hr); return xstrdup (hr == MAPI_E_NOT_FOUND? "[No message class property]": "[Error getting message class property]"); } if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) retstr = xstrdup (propval->Value.lpszA); else retstr = xstrdup ("[Invalid message class property]"); MAPIFreeBuffer (propval); return retstr; } /* Return the old message class. This function returns NULL if no old message class has been saved. Caller needs to release the returned string. */ char * mapi_get_old_message_class (LPMESSAGE message) { HRESULT hr; ULONG tag; LPSPropValue propval = NULL; char *retstr; if (!message) return NULL; if (get_gpgololdmsgclass_tag (message, &tag)) return NULL; hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n", SRCNAME, __func__, hr); return NULL; } if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) retstr = xstrdup (propval->Value.lpszA); else retstr = NULL; MAPIFreeBuffer (propval); return retstr; } /* Return the sender of the message. According to the specs this is an UTF-8 string; we rely on that the UI server handles internationalized domain names. */ char * mapi_get_sender (LPMESSAGE message) { HRESULT hr; LPSPropValue propval = NULL; char *buf; char *p0, *p; if (!message) return NULL; /* No message: Nop. */ hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval); if (FAILED (hr)) { log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n", SRCNAME, __func__, hr); return NULL; } if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) { log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n", SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) ); MAPIFreeBuffer (propval); return NULL; } buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); if (!buf) { log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__); return NULL; } /* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields using Ctrl-A as delimiter. The first field looks like the ascii formatted number of fields to follow, the second field like the email account and the third seems to be a textual description of that account. We return the second field. */ p = strchr (buf, '\x01'); if (!p) { log_error ("%s:%s: unknown format of the value `%s'\n", SRCNAME, __func__, buf); xfree (buf); return NULL; } for (p0=buf, p++; *p && *p != '\x01';) *p0++ = *p++; *p0 = 0; /* When using an Exchange account this is an X.509 address and not an SMTP address. We try to detect this here and extract only the CN RDN. Note that there are two CNs. This is just a simple approach and not a real parser. A better way to do this would be to ask MAPI to resolve the X.500 name to an SMTP name. */ if (strstr (buf, "/o=") && strstr (buf, "/ou=") && (p = strstr (buf, "/cn=Recipients")) && (p = strstr (p+1, "/cn="))) { log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__, buf); memmove (buf, p+4, strlen (p+4)+1); if (!strchr (buf, '@')) { /* Some Exchange accounts return only the accoutn name and no rfc821 mail address. Kleopatra chokes on that, thus we append a domain name. Thisis a bad hack. */ char *newbuf = (char *)xmalloc (strlen (buf) + 6 + 1); strcpy (stpcpy (newbuf, buf), "@local"); xfree (buf); buf = newbuf; } } log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf); return buf; } static char * resolve_ex_from_address (LPMESSAGE message) { HRESULT hr; char *sender_entryid; size_t entryidlen; LPMAPISESSION session; ULONG utype; LPUNKNOWN user; LPSPropValue propval = NULL; char *buf; if (g_ol_version_major < 14) { log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__); return NULL; } sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID, &entryidlen); if (!sender_entryid) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); return NULL; } session = get_oom_mapi_session (); if (!session) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); xfree (sender_entryid); return NULL; } hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid, &IID_IMailUser, MAPI_BEST_ACCESS | MAPI_CACHE_ONLY, &utype, (IUnknown**)&user); if (FAILED (hr)) { log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.", SRCNAME, __func__); hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid, &IID_IMailUser, MAPI_BEST_ACCESS, &utype, (IUnknown**)&user); } gpgol_release (session); if (FAILED (hr)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); return NULL; } hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval); if (FAILED (hr)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); return NULL; } if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) { log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n", SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) ); MAPIFreeBuffer (propval); return NULL; } buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); return buf; } /* Return the from address of the message as a malloced UTF-8 string. Returns NULL if that address is not available. */ char * mapi_get_from_address (LPMESSAGE message) { HRESULT hr; LPSPropValue propval = NULL; char *buf; ULONG try_props[3] = {PidTagSenderSmtpAddress_W, PR_SENT_REPRESENTING_SMTP_ADDRESS_W, PR_SENDER_EMAIL_ADDRESS_W}; if (!message) return xstrdup ("[no message]"); /* Ooops. */ for (int i = 0; i < 3; i++) { /* We try to get different properties first as they contain the SMTP address of the sender. EMAIL address can be some LDAP stuff for exchange. */ hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i], &propval); if (!FAILED (hr)) { break; } } /* This is the last result that should always work but not necessarily contain an SMTP Address. */ if (FAILED (hr)) { log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n", SRCNAME, __func__, hr); return NULL; } if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) { log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n", SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) ); MAPIFreeBuffer (propval); return NULL; } buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); if (!buf) { log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__); return NULL; } if (strstr (buf, "/o=")) { char *buf2; /* If both SMTP Address properties are not set we need to fallback to resolve the address through the address book */ log_debug ("%s:%s: resolving exchange address.", SRCNAME, __func__); buf2 = resolve_ex_from_address (message); if (buf2) { xfree (buf); return buf2; } } return buf; } /* Return the subject of the message as a malloced UTF-8 string. Returns a replacement string if a subject is missing. */ char * mapi_get_subject (LPMESSAGE message) { HRESULT hr; LPSPropValue propval = NULL; char *buf; if (!message) return xstrdup ("[no message]"); /* Ooops. */ hr = HrGetOneProp ((LPMAPIPROP)message, PR_SUBJECT_W, &propval); if (FAILED (hr)) { log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n", SRCNAME, __func__, hr); return xstrdup (_("[no subject]")); } if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) { log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n", SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) ); MAPIFreeBuffer (propval); return xstrdup (_("[no subject]")); } buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); if (!buf) { log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__); return xstrdup (_("[no subject]")); } return buf; } /* Return the message type. This function knows only about our own message types. Returns MSGTYPE_UNKNOWN for any MESSAGE we have no special support for. */ msgtype_t mapi_get_message_type (LPMESSAGE message) { HRESULT hr; ULONG tag; LPSPropValue propval = NULL; msgtype_t msgtype = MSGTYPE_UNKNOWN; if (!message) return msgtype; if (get_gpgolmsgclass_tag (message, &tag) ) return msgtype; /* Ooops */ hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) { hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n", SRCNAME, __func__, hr); return msgtype; } } else log_debug ("%s:%s: have override message class\n", SRCNAME, __func__); if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) { msgtype = string_to_type (propval->Value.lpszA); } MAPIFreeBuffer (propval); return msgtype; } /* This function is pretty useless because IConverterSession won't take attachments into account. Need to write our own version. */ int mapi_to_mime (LPMESSAGE message, const char *filename) { HRESULT hr; LPCONVERTERSESSION session; LPSTREAM stream; hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER, IID_IConverterSession, (void **) &session); if (FAILED (hr)) { log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx", SRCNAME, __func__, hr); return -1; } hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer, (STGM_CREATE | STGM_READWRITE), (char*)filename, NULL, &stream); if (FAILED (hr)) { log_error ("%s:%s: can't create file `%s': hr=%#lx\n", SRCNAME, __func__, filename, hr); hr = -1; } else { hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP); if (FAILED (hr)) { log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx", SRCNAME, __func__, hr); stream->Revert (); hr = -1; } else { stream->Commit (0); hr = 0; } gpgol_release (stream); } gpgol_release (session); return hr; } /* Return a binary property in a malloced buffer with its length stored at R_NBYTES. Returns NULL on error. */ char * mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes) { HRESULT hr; LPSPropValue propval = NULL; char *data; *r_nbytes = 0; hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval); if (FAILED (hr)) { log_error ("%s:%s: error getting property %#lx: hr=%#lx", SRCNAME, __func__, proptype, hr); return NULL; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_BINARY: /* This is a binary object but we know that it must be plain ASCII due to the armored format. */ data = (char*)xmalloc (propval->Value.bin.cb + 1); memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb); data[propval->Value.bin.cb] = 0; *r_nbytes = propval->Value.bin.cb; break; default: log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n", SRCNAME, __func__, proptype, propval->ulPropTag); data = NULL; break; } MAPIFreeBuffer (propval); return data; } /* Return an integer property at R_VALUE. On error the function returns -1 and sets R_VALUE to 0, on success 0 is returned. */ int mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value) { int rc = -1; HRESULT hr; LPSPropValue propval = NULL; *r_value = 0; hr = HrGetOneProp (object, proptype, &propval); if (FAILED (hr)) { log_error ("%s:%s: error getting property %#lx: hr=%#lx", SRCNAME, __func__, proptype, hr); return -1; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_LONG: *r_value = propval->Value.l; rc = 0; break; default: log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n", SRCNAME, __func__, proptype, propval->ulPropTag); break; } MAPIFreeBuffer (propval); return rc; } /* Return the attachment method for attachment OBJ. In case of error we return 0 which happens not to be defined. */ static int get_attach_method (LPATTACH obj) { HRESULT hr; LPSPropValue propval = NULL; int method ; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval); if (FAILED (hr)) { log_error ("%s:%s: error getting attachment method: hr=%#lx", SRCNAME, __func__, hr); return 0; } /* We don't bother checking whether we really get a PT_LONG ulong back; if not the system is seriously damaged and we can't do further harm by returning a possible random value. */ method = propval->Value.l; MAPIFreeBuffer (propval); return method; } /* Return the filename from the attachment as a malloced string. The encoding we return will be UTF-8, however the MAPI docs declare that MAPI does only handle plain ANSI and thus we don't really care later on. In fact we would need to convert the filename back to wchar and use the Unicode versions of the file API. Returns NULL on error or if no filename is available. */ static char * get_attach_filename (LPATTACH obj) { HRESULT hr; LPSPropValue propval; char *name = NULL; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval); if (FAILED(hr)) hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval); if (FAILED(hr)) { log_debug ("%s:%s: no filename property found", SRCNAME, __func__); return NULL; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_UNICODE: name = wchar_to_utf8 (propval->Value.lpszW); if (!name) log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__); break; case PT_STRING8: name = xstrdup (propval->Value.lpszA); break; default: log_debug ("%s:%s: proptag=%#lx not supported\n", SRCNAME, __func__, propval->ulPropTag); name = NULL; break; } MAPIFreeBuffer (propval); return name; } /* Return the content-id of the attachment OBJ or NULL if it does not exists. Caller must free. */ static char * get_attach_content_id (LPATTACH obj) { HRESULT hr; LPSPropValue propval = NULL; char *name; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval); if (FAILED (hr)) { if (hr != MAPI_E_NOT_FOUND) log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx", SRCNAME, __func__, hr); return NULL; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_UNICODE: name = wchar_to_utf8 (propval->Value.lpszW); if (!name) log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__); break; case PT_STRING8: name = xstrdup (propval->Value.lpszA); break; default: log_debug ("%s:%s: proptag=%#lx not supported\n", SRCNAME, __func__, propval->ulPropTag); name = NULL; break; } MAPIFreeBuffer (propval); return name; } /* Return the content-type of the attachment OBJ or NULL if it does not exists. Caller must free. */ static char * get_attach_mime_tag (LPATTACH obj) { HRESULT hr; LPSPropValue propval = NULL; char *name; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval); if (FAILED (hr)) { if (hr != MAPI_E_NOT_FOUND) log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx", SRCNAME, __func__, hr); return NULL; } switch ( PROP_TYPE (propval->ulPropTag) ) { case PT_UNICODE: name = wchar_to_utf8 (propval->Value.lpszW); if (!name) log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__); break; case PT_STRING8: name = xstrdup (propval->Value.lpszA); break; default: log_debug ("%s:%s: proptag=%#lx not supported\n", SRCNAME, __func__, propval->ulPropTag); name = NULL; break; } MAPIFreeBuffer (propval); return name; } /* Return the GpgOL Attach Type for attachment OBJ. Tag needs to be the tag of that property. */ attachtype_t get_gpgolattachtype (LPATTACH obj, ULONG tag) { HRESULT hr; LPSPropValue propval = NULL; attachtype_t retval; hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval); if (FAILED (hr)) { if (hr != MAPI_E_NOT_FOUND) log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx", SRCNAME, __func__, hr); return ATTACHTYPE_UNKNOWN; } retval = (attachtype_t)propval->Value.l; MAPIFreeBuffer (propval); return retval; } /* Gather information about attachments and return a new table of attachments. Caller must release the returned table.s The routine will return NULL in case of an error or if no attachments are available. With FAST set only some information gets collected. */ mapi_attach_item_t * mapi_create_attach_table (LPMESSAGE message, int fast) { HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; mapi_attach_item_t *table; unsigned int pos, n_attach; ULONG moss_tag; if (get_gpgolattachtype_tag (message, &moss_tag) ) return NULL; /* Open the attachment table. */ hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); return NULL; } hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); return NULL; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; log_debug ("%s:%s: message has %u attachments\n", SRCNAME, __func__, n_attach); if (!n_attach) { FreeProws (mapirows); gpgol_release (mapitable); return NULL; } /* Allocate our own table. */ table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table); for (pos=0; pos < n_attach; pos++) { LPATTACH att; if (mapirows->aRow[pos].cValues < 1) { log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos); table[pos].mapipos = -1; continue; } if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM) { log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos); table[pos].mapipos = -1; continue; } table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l; hr = message->OpenAttach (table[pos].mapipos, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx", SRCNAME, __func__, pos, table[pos].mapipos, hr); table[pos].mapipos = -1; continue; } table[pos].method = get_attach_method (att); table[pos].filename = fast? NULL : get_attach_filename (att); table[pos].content_type = fast? NULL : get_attach_mime_tag (att); table[pos].content_id = fast? NULL : get_attach_content_id (att); if (table[pos].content_type) { char *p = strchr (table[pos].content_type, ';'); if (p) { *p++ = 0; trim_trailing_spaces (table[pos].content_type); while (strchr (" \t\r\n", *p)) p++; trim_trailing_spaces (p); table[pos].content_type_parms = p; } } table[pos].attach_type = get_gpgolattachtype (att, moss_tag); gpgol_release (att); } table[0].private_mapitable = mapitable; FreeProws (mapirows); table[pos].end_of_table = 1; mapitable = NULL; if (fast) { log_debug ("%s:%s: attachment info: not shown due to fast flag\n", SRCNAME, __func__); } else { log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__); for (pos=0; !table[pos].end_of_table; pos++) { log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n", table[pos].mapipos, table[pos].attach_type, table[pos].filename, table[pos].content_type, table[pos].content_type_parms); } } return table; } /* Release a table as created by mapi_create_attach_table. */ void mapi_release_attach_table (mapi_attach_item_t *table) { unsigned int pos; LPMAPITABLE mapitable; if (!table) return; mapitable = (LPMAPITABLE)table[0].private_mapitable; if (mapitable) gpgol_release (mapitable); for (pos=0; !table[pos].end_of_table; pos++) { xfree (table[pos].filename); xfree (table[pos].content_type); xfree (table[pos].content_id); } xfree (table); } /* Return an attachment as a new IStream object. Returns NULL on failure. If R_ATTACH is not NULL the actual attachment will not be released but stored at that address; the caller needs to release it in this case. */ LPSTREAM mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item, LPATTACH *r_attach) { HRESULT hr; LPATTACH att; LPSTREAM stream; if (r_attach) *r_attach = NULL; if (!item || item->end_of_table || item->mapipos == -1) return NULL; hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment at %d: hr=%#lx", SRCNAME, __func__, item->mapipos, hr); return NULL; } if (item->method != ATTACH_BY_VALUE) { log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__); gpgol_release (att); return NULL; } hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN*) &stream); if (FAILED (hr)) { log_error ("%s:%s: can't open data stream of attachment: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (att); return NULL; } if (r_attach) *r_attach = att; else gpgol_release (att); return stream; } /* Return a malloced buffer with the content of the attachment. If R_NBYTES is not NULL the number of bytes will get stored there. ATT must have an attachment method of ATTACH_BY_VALUE. Returns NULL on error. If UNPROTECT is set and the appropriate crypto attribute is available, the function returns the unprotected version of the atatchment. */ static char * attach_to_buffer (LPATTACH att, size_t *r_nbytes) { HRESULT hr; LPSTREAM stream; STATSTG statInfo; ULONG nread; char *buffer; hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN*) &stream); if (FAILED (hr)) { log_error ("%s:%s: can't open data stream of attachment: hr=%#lx", SRCNAME, __func__, hr); return NULL; } hr = stream->Stat (&statInfo, STATFLAG_NONAME); if ( hr != S_OK ) { log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); return NULL; } /* Allocate one byte more so that we can terminate the string. */ buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1); hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread); if ( hr != S_OK ) { log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (buffer); gpgol_release (stream); return NULL; } if (nread != statInfo.cbSize.QuadPart) { log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (buffer); buffer = NULL; } gpgol_release (stream); /* Make sure that the buffer is a C string. */ if (buffer) buffer[nread] = 0; if (r_nbytes) *r_nbytes = nread; return buffer; } /* Return an attachment as a malloced buffer. The size of the buffer will be stored at R_NBYTES. If unprotect is true, the atatchment will be unprotected. Returns NULL on failure. */ char * mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes) { HRESULT hr; LPATTACH att; char *buffer; if (!item || item->end_of_table || item->mapipos == -1) return NULL; hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment at %d: hr=%#lx", SRCNAME, __func__, item->mapipos, hr); return NULL; } if (item->method != ATTACH_BY_VALUE) { log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__); gpgol_release (att); return NULL; } buffer = attach_to_buffer (att, r_nbytes); gpgol_release (att); return buffer; } /* Mark this attachment as the original MOSS message. We set a custom property as well as the hidden flag. */ int mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item) { int retval = -1; HRESULT hr; LPATTACH att; SPropValue prop; if (!item || item->end_of_table || item->mapipos == -1) return -1; hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment at %d: hr=%#lx", SRCNAME, __func__, item->mapipos, hr); return -1; } if (get_gpgolattachtype_tag (message, &prop.ulPropTag) ) goto leave; prop.Value.l = ATTACHTYPE_MOSS; hr = HrSetOneProp (att, &prop); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Attach Type", hr); return false; } prop.ulPropTag = PR_ATTACHMENT_HIDDEN; prop.Value.b = TRUE; hr = HrSetOneProp (att, &prop); if (hr) { log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } hr = att->SaveChanges (KEEP_OPEN_READWRITE); if (hr) { log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } retval = 0; leave: gpgol_release (att); return retval; } /* If the hidden property has not been set on ATTACH, set it and save the changes. */ int mapi_set_attach_hidden (LPATTACH attach) { int retval = -1; HRESULT hr; LPSPropValue propval; SPropValue prop; hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval); if (SUCCEEDED (hr) && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b) return 0;/* Already set to hidden. */ prop.ulPropTag = PR_ATTACHMENT_HIDDEN; prop.Value.b = TRUE; hr = HrSetOneProp (attach, &prop); if (hr) { log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } hr = attach->SaveChanges (KEEP_OPEN_READWRITE); if (hr) { log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } retval = 0; leave: return retval; } /* Returns true if ATTACH has the hidden flag set to true. */ int mapi_test_attach_hidden (LPATTACH attach) { HRESULT hr; LPSPropValue propval = NULL; int result = 0; hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval); if (FAILED (hr)) return result; /* No. */ if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b) result = 1; /* Yes. */ MAPIFreeBuffer (propval); return result; } /* Returns True if MESSAGE has the GpgOL Sig Status property. */ int mapi_has_sig_status (LPMESSAGE msg) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; int yes; if (get_gpgolsigstatus_tag (msg, &tag) ) return 0; /* Error: Assume No. */ hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) return 0; /* No. */ if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) yes = 1; else yes = 0; MAPIFreeBuffer (propval); return yes; } /* Returns True if MESSAGE has a GpgOL Sig Status property and that it is not set to unchecked. */ int mapi_test_sig_status (LPMESSAGE msg) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; int yes; if (get_gpgolsigstatus_tag (msg, &tag) ) return 0; /* Error: Assume No. */ hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) return 0; /* No. */ /* We return False if we have an unknown signature status (?) or the message has been sent by us and not yet checked (@). */ if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?") || !strcmp (propval->Value.lpszA, "@"))); else yes = 0; MAPIFreeBuffer (propval); return yes; } /* Return the signature status as an allocated string. Will never return NULL. */ char * mapi_get_sig_status (LPMESSAGE msg) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgolsigstatus_tag (msg, &tag) ) return xstrdup ("[Error getting tag for sig status]"); hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) return xstrdup (""); if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) retstr = xstrdup (propval->Value.lpszA); else retstr = xstrdup ("[Sig status has an invalid type]"); MAPIFreeBuffer (propval); return retstr; } /* Set the signature status property to STATUS_STRING. There are a few special values: "#" The message is not of interest to us. "@" The message has been created and signed or encrypted by us. "?" The signature status has not been checked. "!" The signature verified okay "~" The signature was not fully verified. "-" The signature is bad Note that this function does not call SaveChanges. */ int mapi_set_sig_status (LPMESSAGE message, const char *status_string) { HRESULT hr; SPropValue prop; if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) ) return -1; prop.Value.lpszA = xstrdup (status_string); hr = HrSetOneProp (message, &prop); xfree (prop.Value.lpszA); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Sig Status", hr); return -1; } return 0; } /* When sending a message we need to fake the message class so that OL processes it according to our needs. However, if we later try to get the message class from the sent message, OL still has the SMIME message class and tries to hide this by trying to decrypt the message and return the message class from the plaintext. To mitigate the problem we define our own msg class override property. */ int mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name) { HRESULT hr; SPropValue prop; if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) ) return -1; prop.Value.lpszA = xstrdup (name); hr = HrSetOneProp (message, &prop); xfree (prop.Value.lpszA); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Msg Class", hr); return -1; } return 0; } /* Return the charset as assigned by GpgOL to an attachment. This may return NULL it is has not been assigned or is the standard (UTF-8). */ char * mapi_get_gpgol_charset (LPMESSAGE obj) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgolcharset_tag (obj, &tag) ) return NULL; /* Error. */ hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval); if (FAILED (hr)) return NULL; if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) { if (!strcmp (propval->Value.lpszA, "utf-8")) retstr = NULL; else retstr = xstrdup (propval->Value.lpszA); } else retstr = NULL; MAPIFreeBuffer (propval); return retstr; } /* Set the GpgOl charset property to an attachment. Note that this function does not call SaveChanges. */ int mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset) { HRESULT hr; SPropValue prop; char *p; /* Note that we lowercase the value and cut it to a max of 32 characters. The latter is required to make sure that HrSetOneProp will always work. */ if (get_gpgolcharset_tag (obj, &prop.ulPropTag) ) return -1; prop.Value.lpszA = xstrdup (charset); for (p=prop.Value.lpszA; *p; p++) *p = tolower (*(unsigned char*)p); if (strlen (prop.Value.lpszA) > 32) prop.Value.lpszA[32] = 0; hr = HrSetOneProp ((LPMAPIPROP)obj, &prop); xfree (prop.Value.lpszA); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Charset", hr); return -1; } return 0; } /* Return GpgOL's draft info string as an allocated string. If no draft info is available, NULL is returned. */ char * mapi_get_gpgol_draft_info (LPMESSAGE msg) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgoldraftinfo_tag (msg, &tag) ) return NULL; hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) return NULL; if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) retstr = xstrdup (propval->Value.lpszA); else retstr = NULL; MAPIFreeBuffer (propval); return retstr; } /* Set GpgOL's draft info string to STRING. This string is defined as: Character 1: 'E' = encrypt selected, 'e' = encrypt not selected. '-' = don't care Character 2: 'S' = sign selected, 's' = sign not selected. '-' = don't care Character 3: 'A' = Auto protocol 'P' = OpenPGP protocol 'X' = S/MIME protocol '-' = don't care If string is NULL, the property will get deleted. Note that this function does not call SaveChanges. */ int mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string) { HRESULT hr; SPropValue prop; SPropTagArray proparray; if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) ) return -1; if (string) { prop.Value.lpszA = xstrdup (string); hr = HrSetOneProp (message, &prop); xfree (prop.Value.lpszA); } else { proparray.cValues = 1; proparray.aulPropTag[0] = prop.ulPropTag; hr = message->DeleteProps (&proparray, NULL); } if (hr) { log_error ("%s:%s: can't %s %s property: hr=%#lx\n", SRCNAME, __func__, string?"set":"delete", "GpgOL Draft Info", hr); return -1; } return 0; } /* Return the MIME info as an allocated string. Will never return NULL. */ char * mapi_get_mime_info (LPMESSAGE msg) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgolmimeinfo_tag (msg, &tag) ) return xstrdup ("[Error getting tag for MIME info]"); hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) return xstrdup (""); if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) retstr = xstrdup (propval->Value.lpszA); else retstr = xstrdup ("[MIME info has an invalid type]"); MAPIFreeBuffer (propval); return retstr; } /* Helper around mapi_get_gpgol_draft_info to avoid the string handling. Return values are: 0 -> Do nothing 1 -> Encrypt 2 -> Sign 3 -> Encrypt & Sign*/ int get_gpgol_draft_info_flags (LPMESSAGE message) { char *buf = mapi_get_gpgol_draft_info (message); int ret = 0; if (!buf) { return 0; } if (buf[0] == 'E') { ret |= 1; } if (buf[1] == 'S') { ret |= 2; } xfree (buf); return ret; } /* Sets the draft info flags. Protocol is always Auto. flags should be the same as defined by get_gpgol_draft_info_flags */ int set_gpgol_draft_info_flags (LPMESSAGE message, int flags) { char buf[4]; buf[3] = '\0'; buf[2] = 'A'; /* Protocol */ buf[1] = flags & 2 ? 'S' : 's'; buf[0] = flags & 1 ? 'E' : 'e'; return mapi_set_gpgol_draft_info (message, buf); } /* Helper for mapi_get_msg_content_type() */ static int get_message_content_type_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg) { (void)dummy_arg; (void)msg; if (event == RFC822PARSE_T2BODY) return 42; /* Hack to stop the parsing after having read the outer headers. */ return 0; } /* Return Content-Type of the current message. This one is taken directly from the rfc822 header. If R_PROTOCOL is not NULL a string with the protocol parameter will be stored at this address, if no protocol is given NULL will be stored. If R_SMTYPE is not NULL a string with the smime-type parameter will be stored there. Caller must release all returned strings. */ char * mapi_get_message_content_type (LPMESSAGE message, char **r_protocol, char **r_smtype) { rfc822parse_t msg; const char *header_lines, *s; rfc822parse_field_t ctx; size_t length; char *retstr = NULL; if (r_protocol) *r_protocol = NULL; if (r_smtype) *r_smtype = NULL; /* Read the headers into an rfc822 object. */ msg = rfc822parse_open (get_message_content_type_cb, NULL); if (!msg) { log_error ("%s:%s: rfc822parse_open failed", SRCNAME, __func__); return NULL; } const std::string hdrStr = mapi_get_header (message); if (hdrStr.empty()) { log_error ("%s:%s: failed to get headers", SRCNAME, __func__); return NULL; } header_lines = hdrStr.c_str(); while ((s = strchr (header_lines, '\n'))) { length = (s - header_lines); if (length && s[-1] == '\r') length--; if (!strncmp ("Wks-Phase: confirm", header_lines, std::max (18, (int) length))) { log_debug ("%s:%s: detected wks confirmation mail", SRCNAME, __func__); retstr = xstrdup ("wks.confirmation.mail"); rfc822parse_close (msg); return retstr; } rfc822parse_insert (msg, (const unsigned char*)header_lines, length); header_lines = s+1; } /* Parse the content-type field. */ ctx = rfc822parse_parse_field (msg, "Content-Type", -1); if (ctx) { const char *s1, *s2; s1 = rfc822parse_query_media_type (ctx, &s2); if (s1) { retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1); strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2); if (r_protocol) { s = rfc822parse_query_parameter (ctx, "protocol", 0); if (s) *r_protocol = xstrdup (s); } if (r_smtype) { s = rfc822parse_query_parameter (ctx, "smime-type", 0); if (s) *r_smtype = xstrdup (s); } } rfc822parse_release_field (ctx); } rfc822parse_close (msg); return retstr; } /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value. This indicates that there should be no PR_BODY tag. */ int mapi_has_last_decrypted (LPMESSAGE message) { HRESULT hr; LPSPropValue propval = NULL; ULONG tag; int yes = 0; if (get_gpgollastdecrypted_tag (message, &tag) ) return 0; /* No. */ hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) return 0; /* No. */ if (PROP_TYPE (propval->ulPropTag) == PT_BINARY) yes = 1; MAPIFreeBuffer (propval); return yes; } /* Helper for mapi_get_gpgol_body_attachment. */ static int has_gpgol_body_name (LPATTACH obj) { HRESULT hr; LPSPropValue propval; int yes = 0; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval); if (FAILED(hr)) return 0; if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE) { if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt")) yes = 1; else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm")) yes = 2; } else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8) { if (!strcmp (propval->Value.lpszA, "gpgol000.txt")) yes = 1; else if (!strcmp (propval->Value.lpszA, "gpgol000.htm")) yes = 2; } MAPIFreeBuffer (propval); return yes; } /* Helper to check whether the file name of OBJ is "smime.p7m". Returns on true if so. */ static int has_smime_filename (LPATTACH obj) { HRESULT hr; LPSPropValue propval; int yes = 0; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval); if (FAILED(hr)) { hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval); if (FAILED(hr)) return 0; } if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE) { if (!wcscmp (propval->Value.lpszW, L"smime.p7m")) yes = 1; } else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8) { if (!strcmp (propval->Value.lpszA, "smime.p7m")) yes = 1; } MAPIFreeBuffer (propval); return yes; } /* Return the content of the body attachment of MESSAGE. The body attachment is a hidden attachment created by us for later display. If R_NBYTES is not NULL the number of bytes in the returned buffer is stored there. If R_ISHTML is not NULL a flag indicating whether the HTML is html formatted is stored there. If R_PROTECTED is not NULL a flag indicating whether the message was protected is stored there. If no body attachment can be found or on any other error an error codes is returned and NULL is stored at R_BODY. Caller must free the returned string. If NULL is passed for R_BODY, the function will only test whether a body attachment is available and return an error code if not. R_IS_HTML and R_PROTECTED are not defined in this case. */ int mapi_get_gpgol_body_attachment (LPMESSAGE message, char **r_body, size_t *r_nbytes, int *r_ishtml, int *r_protected) { HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; unsigned int pos, n_attach; ULONG moss_tag; char *body = NULL; int bodytype; int found = 0; if (r_body) *r_body = NULL; if (r_ishtml) *r_ishtml = 0; if (r_protected) *r_protected = 0; if (get_gpgolattachtype_tag (message, &moss_tag) ) return -1; hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); return -1; } hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); return -1; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (!n_attach) { FreeProws (mapirows); gpgol_release (mapitable); log_debug ("%s:%s: No attachments at all", SRCNAME, __func__); return -1; } log_debug ("%s:%s: message has %u attachments\n", SRCNAME, __func__, n_attach); for (pos=0; pos < n_attach; pos++) { LPATTACH att; if (mapirows->aRow[pos].cValues < 1) { log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos); continue; } if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM) { log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos); continue; } hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx", SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); continue; } if ((bodytype=has_gpgol_body_name (att)) && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS) { found = 1; if (!r_body) ; /* Body content has not been requested. */ else if (opt.body_as_attachment && !mapi_test_attach_hidden (att)) { /* The body is to be shown as an attachment. */ body = native_to_utf8 (bodytype == 2 ? ("[Open the attachment \"gpgol000.htm\"" " to view the message.]") : ("[Open the attachment \"gpgol000.txt\"" " to view the message.]")); found = 1; } else { char *charset; if (get_attach_method (att) == ATTACH_BY_VALUE) body = attach_to_buffer (att, r_nbytes); if (body && (charset = mapi_get_gpgol_charset ((LPMESSAGE)att))) { /* We only support transcoding from Latin-1 for now. */ if (strcmp (charset, "iso-8859-1") && !strcmp (charset, "latin-1")) log_debug ("%s:%s: Using Latin-1 instead of %s", SRCNAME, __func__, charset); xfree (charset); charset = latin1_to_utf8 (body); xfree (body); body = charset; } } gpgol_release (att); if (r_ishtml) *r_ishtml = (bodytype == 2); break; } gpgol_release (att); } FreeProws (mapirows); gpgol_release (mapitable); if (!found) { log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__); if (r_body) *r_body = native_to_utf8 (_("[The content of this message is not visible" " due to an processing error in GpgOL.]")); return -1; } if (r_body) *r_body = body; else xfree (body); /* (Should not happen.) */ return 0; } /* Delete a possible body atatchment. Returns true if an atatchment has been deleted. */ int mapi_delete_gpgol_body_attachment (LPMESSAGE message) { HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; unsigned int pos, n_attach; ULONG moss_tag; int found = 0; if (get_gpgolattachtype_tag (message, &moss_tag) ) return 0; hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); return 0; } hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); return 0; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (!n_attach) { FreeProws (mapirows); gpgol_release (mapitable); return 0; /* No Attachments. */ } for (pos=0; pos < n_attach; pos++) { LPATTACH att; if (mapirows->aRow[pos].cValues < 1) { log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos); continue; } if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM) { log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos); continue; } hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx", SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); continue; } if (has_gpgol_body_name (att) && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS) { gpgol_release (att); hr = message->DeleteAttach (mapirows->aRow[pos].lpProps[0].Value.l, 0, NULL, 0); if (hr) log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n", SRCNAME, __func__, hr); else { log_debug ("%s:%s: body attachment deleted\n", SRCNAME, __func__); found = 1; } break; } gpgol_release (att); } FreeProws (mapirows); gpgol_release (mapitable); return found; } /* Copy the attachment ITEM of the message MESSAGE verbatim to the PR_BODY property. Returns 0 on success. This function does not call SaveChanges. */ int mapi_attachment_to_body (LPMESSAGE message, mapi_attach_item_t *item) { int result = -1; HRESULT hr; LPATTACH att = NULL; LPSTREAM instream = NULL; LPSTREAM outstream = NULL; LPUNKNOWN punk; if (!message || !item || item->end_of_table || item->mapipos == -1) return -1; /* Error. */ hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment at %d: hr=%#lx", SRCNAME, __func__, item->mapipos, hr); goto leave; } if (item->method != ATTACH_BY_VALUE) { log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__); goto leave; } hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN*) &instream); if (FAILED (hr)) { log_error ("%s:%s: can't open data stream of attachment: hr=%#lx", SRCNAME, __func__, hr); goto leave; } punk = (LPUNKNOWN)outstream; hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, MAPI_CREATE|MAPI_MODIFY, &punk); if (FAILED (hr)) { log_error ("%s:%s: can't open body stream for update: hr=%#lx", SRCNAME, __func__, hr); goto leave; } outstream = (LPSTREAM)punk; { ULARGE_INTEGER cb; cb.QuadPart = 0xffffffffffffffffll; hr = instream->CopyTo (outstream, cb, NULL, NULL); } if (hr) { log_error ("%s:%s: can't copy streams: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } hr = outstream->Commit (0); if (hr) { log_error ("%s:%s: commiting output stream failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } result = 0; leave: if (outstream) { if (result) outstream->Revert (); gpgol_release (outstream); } if (instream) gpgol_release (instream); if (att) gpgol_release (att); return result; } /* Copy the MAPI body to a PGPBODY type attachment. */ int mapi_body_to_attachment (LPMESSAGE message) { HRESULT hr; LPSTREAM instream; ULONG newpos; LPATTACH newatt = NULL; SPropValue prop; LPSTREAM outstream = NULL; LPUNKNOWN punk; char body_filename[] = PGPBODYFILENAME; instream = mapi_get_body_as_stream (message); if (!instream) return -1; hr = message->CreateAttach (NULL, 0, &newpos, &newatt); if (hr) { log_error ("%s:%s: can't create attachment: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } prop.ulPropTag = PR_ATTACH_METHOD; prop.Value.ul = ATTACH_BY_VALUE; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set attach method: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } /* Mark that attachment so that we know why it has been created. */ if (get_gpgolattachtype_tag (message, &prop.ulPropTag) ) goto leave; prop.Value.l = ATTACHTYPE_PGPBODY; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Attach Type", hr); goto leave; } prop.ulPropTag = PR_ATTACHMENT_HIDDEN; prop.Value.b = TRUE; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } prop.ulPropTag = PR_ATTACH_FILENAME_A; prop.Value.lpszA = body_filename; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set attach filename: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } punk = (LPUNKNOWN)outstream; hr = newatt->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 leave; } outstream = (LPSTREAM)punk; /* Insert a blank line so that our mime parser skips over the mail headers. */ hr = outstream->Write ("\r\n", 2, NULL); if (hr) { log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } { ULARGE_INTEGER cb; cb.QuadPart = 0xffffffffffffffffll; hr = instream->CopyTo (outstream, cb, NULL, NULL); } if (hr) { log_error ("%s:%s: can't copy streams: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } hr = outstream->Commit (0); if (hr) { log_error ("%s:%s: Commiting output stream failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } gpgol_release (outstream); outstream = NULL; hr = newatt->SaveChanges (0); if (hr) { log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } gpgol_release (newatt); newatt = NULL; hr = mapi_save_changes (message, KEEP_OPEN_READWRITE); leave: if (outstream) { outstream->Revert (); gpgol_release (outstream); } if (newatt) gpgol_release (newatt); gpgol_release (instream); return hr? -1:0; } int mapi_mark_or_create_moss_attach (LPMESSAGE message, msgtype_t msgtype) { int i; if (msgtype == MSGTYPE_UNKNOWN || msgtype == MSGTYPE_GPGOL) { return 0; } /* First check if we already have one marked. */ mapi_attach_item_t *table = mapi_create_attach_table (message, 0); int part1 = 0, part2 = 0; for (i = 0; table && !table[i].end_of_table; i++) { if (table[i].attach_type == ATTACHTYPE_PGPBODY || table[i].attach_type == ATTACHTYPE_MOSS || table[i].attach_type == ATTACHTYPE_MOSSTEMPL) { if (!part1) { part1 = i + 1; } else if (!part2) { /* If we have two MOSS attachments we use the second one. */ part2 = i + 1; break; } } } if (part1 || part2) { /* Found existing moss attachment */ mapi_release_attach_table (table); /* Remark to ensure that it is hidden. As our revert code must unhide it so that it is not stored in winmail.dat but used as the mosstmpl. */ mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1); LPATTACH att; if (message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att) != S_OK) { log_error ("%s:%s: can't open attachment at %d", SRCNAME, __func__, item->mapipos); return -1; } if (!mapi_test_attach_hidden (att)) { mapi_set_attach_hidden (att); } gpgol_release (att); if (part2) return part2; return part1; } if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED || msgtype == MSGTYPE_GPGOL_PGP_MESSAGE) { /* Inline message we need to create body attachment so that we are able to restore the content. */ if (mapi_body_to_attachment (message)) { log_error ("%s:%s: Failed to create body attachment.", SRCNAME, __func__); return 0; } log_debug ("%s:%s: Created body attachment. Repeating lookup.", SRCNAME, __func__); /* The position of the MOSS attach might change depending on the attachment count of the mail. So repeat the check to get the right position. */ return mapi_mark_or_create_moss_attach (message, msgtype); } if (!table) { log_debug ("%s:%s: Neither pgp inline nor an attachment table.", SRCNAME, __func__); return 0; } /* MIME Mails check for S/MIME first. */ for (i = 0; !table[i].end_of_table; i++) { if (table[i].content_type && (!strcmp (table[i].content_type, "application/pkcs7-mime") || !strcmp (table[i].content_type, "application/x-pkcs7-mime")) && table[i].filename && !strcmp (table[i].filename, "smime.p7m")) break; } if (!table[i].end_of_table) { mapi_mark_moss_attach (message, table + i); mapi_release_attach_table (table); return i + 1; } /* PGP/MIME or S/MIME stuff. */ /* Multipart/encrypted message: We expect 2 attachments. The first one with the version number and the second one with the ciphertext. As we don't know wether we are called the first time, we first try to find these attachments by looking at all attachments. Only if this fails we identify them by their order (i.e. the first 2 attachments) and mark them as part1 and part2. */ for (i = 0; !table[i].end_of_table; i++); /* Count entries */ if (i >= 2) { int part1_idx = -1, part2_idx = -1; /* At least 2 attachments but none are marked. Thus we assume that this is the first time we see this message and we will set the mark now if we see appropriate content types. */ if (table[0].content_type && !strcmp (table[0].content_type, "application/pgp-encrypted")) part1_idx = 0; if (table[1].content_type && !strcmp (table[1].content_type, "application/octet-stream")) part2_idx = 1; if (part1_idx != -1 && part2_idx != -1) { mapi_mark_moss_attach (message, table+part1_idx); mapi_mark_moss_attach (message, table+part2_idx); mapi_release_attach_table (table); return 2; } } if (!table[0].end_of_table && table[1].end_of_table) { /* No MOSS flag found in the table but there is only one attachment. Due to the message type we know that this is the original MOSS message. We mark this attachment as hidden, so that it won't get displayed. We further mark it as our original MOSS attachment so that after parsing we have a mean to find it again (see above). */ mapi_mark_moss_attach (message, table + 0); mapi_release_attach_table (table); return 1; } mapi_release_attach_table (table); return 0; /* No original attachment - this should not happen. */ } diff --git a/src/mymapitags.h b/src/mymapitags.h index 1458102..69d1eef 100644 --- a/src/mymapitags.h +++ b/src/mymapitags.h @@ -1,1078 +1,1079 @@ /* mymapitags.h - MAPI definitions * * This file defines constants as used by MAPI. This interface * definition has been compiled from similar Python code by g10 Code * GmbH. * * Revisions: * 2005-07-26 Initial version. * */ #ifndef MAPITAGS_H #define MAPITAGS_H 1 #define PT_UNSPECIFIED 0 #define PT_NULL 1 #define PT_I2 2 #define PT_LONG 3 #define PT_R4 4 #define PT_DOUBLE 5 #define PT_CURRENCY 6 #define PT_APPTIME 7 #define PT_ERROR 10 #define PT_BOOLEAN 11 #define PT_OBJECT 13 #define PT_I8 20 #define PT_STRING8 30 #define PT_UNICODE 31 #define PT_SYSTIME 64 #define PT_CLSID 72 #define PT_BINARY 258 #define PT_SHORT PT_I2 #define PT_I4 PT_LONG #define PT_FLOAT PT_R4 #define PT_R8 PT_DOUBLE #define PT_LONGLONG PT_I8 #define MV_FLAG 0x1000 #define PT_MV_I2 (MV_FLAG|PT_I2) #define PT_MV_LONG (MV_FLAG|PT_LONG) #define PT_MV_R4 (MV_FLAG|PT_R4) #define PT_MV_DOUBLE (MV_FLAG|PT_DOUBLE) #define PT_MV_CURRENCY (MV_FLAG|PT_CURRENCY) #define PT_MV_APPTIME (MV_FLAG|PT_APPTIME) #define PT_MV_SYSTIME (MV_FLAG|PT_SYSTIME) #define PT_MV_STRING8 (MV_FLAG|PT_STRING8) #define PT_MV_BINARY (MV_FLAG|PT_BINARY) #define PT_MV_UNICODE (MV_FLAG|PT_UNICODE) #define PT_MV_CLSID (MV_FLAG|PT_CLSID) #define PT_MV_I8 (MV_FLAG|PT_I8) #define PT_MV_SHORT PT_MV_I2 #define PT_MV_I4 PT_MV_LONG #define PT_MV_FLOAT PT_MV_R4 #define PT_MV_R8 PT_MV_DOUBLE #define PT_MV_LONGLONG PT_MV_I8 #define PT_TSTRING PT_UNICODE #define PT_MV_TSTRING (MV_FLAG|PT_UNICODE) #define PROP_TYPE_MASK 0x0000FFFF #define PROP_TYPE(t) ((t) & PROP_TYPE_MASK) #define PROP_ID(t) ((t)>>16) #define PROP_TAG(t,i) (((i)<<16)|(t)) #define PROP_ID_NULL 0 #define PROP_ID_INVALID 0xFFFF #define PR_NULL PROP_TAG(PT_NULL, PROP_ID_NULL) #define PR_ACKNOWLEDGEMENT_MODE PROP_TAG( PT_LONG, 0x0001) #define PR_ACKNOWLEDGEMENT_MODE PROP_TAG( PT_LONG, 0x0001) #define PR_ALTERNATE_RECIPIENT_ALLOWED PROP_TAG( PT_BOOLEAN, 0x0002) #define PR_AUTHORIZING_USERS PROP_TAG( PT_BINARY, 0x0003) #define PR_AUTO_FORWARD_COMMENT PROP_TAG( PT_TSTRING, 0x0004) #define PR_AUTO_FORWARD_COMMENT_W PROP_TAG( PT_UNICODE, 0x0004) #define PR_AUTO_FORWARD_COMMENT_W PROP_TAG( PT_UNICODE, 0x0004) #define PR_AUTO_FORWARD_COMMENT_A PROP_TAG( PT_STRING8, 0x0004) #define PR_AUTO_FORWARDED PROP_TAG( PT_BOOLEAN, 0x0005) #define PR_CONTENT_TYPE_A PROP_TAG( PT_STRING8, 0x8095) #define PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID PROP_TAG( PT_BINARY, 0x0006) #define PR_CONTENT_CORRELATOR PROP_TAG( PT_BINARY, 0x0007) #define PR_CONTENT_IDENTIFIER PROP_TAG( PT_TSTRING, 0x0008) #define PR_CONTENT_IDENTIFIER_W PROP_TAG( PT_UNICODE, 0x0008) #define PR_CONTENT_IDENTIFIER_A PROP_TAG( PT_STRING8, 0x0008) #define PR_CONTENT_LENGTH PROP_TAG( PT_LONG, 0x0009) #define PR_CONTENT_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x000A) #define PR_CONVERSATION_KEY PROP_TAG( PT_BINARY, 0x000B) #define PR_CONVERSION_EITS PROP_TAG( PT_BINARY, 0x000C) #define PR_CONVERSION_WITH_LOSS_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x000D) #define PR_CONVERTED_EITS PROP_TAG( PT_BINARY, 0x000E) #define PR_DEFERRED_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x000F) #define PR_DELIVER_TIME PROP_TAG( PT_SYSTIME, 0x0010) #define PR_DISCARD_REASON PROP_TAG( PT_LONG, 0x0011) #define PR_DISCLOSURE_OF_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x0012) #define PR_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x0013) #define PR_DL_EXPANSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0014) #define PR_EXPIRY_TIME PROP_TAG( PT_SYSTIME, 0x0015) #define PR_IMPLICIT_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0016) #define PR_IMPORTANCE PROP_TAG( PT_LONG, 0x0017) #define PR_IPM_ID PROP_TAG( PT_BINARY, 0x0018) #define PR_LATEST_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0019) #define PR_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x001A) #define PR_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x001A) #define PR_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x001A) #define PR_MESSAGE_DELIVERY_ID PROP_TAG( PT_BINARY, 0x001B) #define PR_MESSAGE_SECURITY_LABEL PROP_TAG( PT_BINARY, 0x001E) #define PR_OBSOLETED_IPMS PROP_TAG( PT_BINARY, 0x001F) #define PR_ORIGINALLY_INTENDED_RECIPIENT_NAME PROP_TAG( PT_BINARY, 0x0020) #define PR_ORIGINAL_EITS PROP_TAG( PT_BINARY, 0x0021) #define PR_ORIGINATOR_CERTIFICATE PROP_TAG( PT_BINARY, 0x0022) #define PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0023) #define PR_ORIGINATOR_RETURN_ADDRESS PROP_TAG( PT_BINARY, 0x0024) #define PR_PARENT_KEY PROP_TAG( PT_BINARY, 0x0025) #define PR_PRIORITY PROP_TAG( PT_LONG, 0x0026) #define PR_ORIGIN_CHECK PROP_TAG( PT_BINARY, 0x0027) #define PR_PROOF_OF_SUBMISSION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0028) #define PR_READ_RECEIPT_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0029) #define PR_RECEIPT_TIME PROP_TAG( PT_SYSTIME, 0x002A) #define PR_RECIPIENT_REASSIGNMENT_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x002B) #define PR_REDIRECTION_HISTORY PROP_TAG( PT_BINARY, 0x002C) #define PR_RELATED_IPMS PROP_TAG( PT_BINARY, 0x002D) #define PR_ORIGINAL_SENSITIVITY PROP_TAG( PT_LONG, 0x002E) #define PR_LANGUAGES PROP_TAG( PT_TSTRING, 0x002F) #define PR_LANGUAGES_W PROP_TAG( PT_UNICODE, 0x002F) #define PR_LANGUAGES_A PROP_TAG( PT_STRING8, 0x002F) #define PR_REPLY_TIME PROP_TAG( PT_SYSTIME, 0x0030) #define PR_REPORT_TAG PROP_TAG( PT_BINARY, 0x0031) #define PR_REPORT_TIME PROP_TAG( PT_SYSTIME, 0x0032) #define PR_RETURNED_IPM PROP_TAG( PT_BOOLEAN, 0x0033) #define PR_SECURITY PROP_TAG( PT_LONG, 0x0034) #define PR_INCOMPLETE_COPY PROP_TAG( PT_BOOLEAN, 0x0035) #define PR_SENSITIVITY PROP_TAG( PT_LONG, 0x0036) #define PR_SUBJECT PROP_TAG( PT_TSTRING, 0x0037) #define PR_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0037) #define PR_SUBJECT_A PROP_TAG( PT_STRING8, 0x0037) #define PR_SUBJECT_IPM PROP_TAG( PT_BINARY, 0x0038) #define PR_CLIENT_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0039) #define PR_REPORT_NAME PROP_TAG( PT_TSTRING, 0x003A) #define PR_REPORT_NAME_W PROP_TAG( PT_UNICODE, 0x003A) #define PR_REPORT_NAME_A PROP_TAG( PT_STRING8, 0x003A) #define PR_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x003B) #define PR_X400_CONTENT_TYPE PROP_TAG( PT_BINARY, 0x003C) #define PR_SUBJECT_PREFIX PROP_TAG( PT_TSTRING, 0x003D) #define PR_SUBJECT_PREFIX_W PROP_TAG( PT_UNICODE, 0x003D) #define PR_SUBJECT_PREFIX_A PROP_TAG( PT_STRING8, 0x003D) #define PR_NON_RECEIPT_REASON PROP_TAG( PT_LONG, 0x003E) #define PR_RECEIVED_BY_ENTRYID PROP_TAG( PT_BINARY, 0x003F) #define PR_RECEIVED_BY_NAME PROP_TAG( PT_TSTRING, 0x0040) #define PR_RECEIVED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x0040) #define PR_RECEIVED_BY_NAME_A PROP_TAG( PT_STRING8, 0x0040) #define PR_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0041) #define PR_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0042) #define PR_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0042) #define PR_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0042) #define PR_RCVD_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x0043) #define PR_RCVD_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x0044) #define PR_RCVD_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x0044) #define PR_RCVD_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x0044) #define PR_REPORT_ENTRYID PROP_TAG( PT_BINARY, 0x0045) #define PR_READ_RECEIPT_ENTRYID PROP_TAG( PT_BINARY, 0x0046) #define PR_MESSAGE_SUBMISSION_ID PROP_TAG( PT_BINARY, 0x0047) #define PR_PROVIDER_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x0048) #define PR_ORIGINAL_SUBJECT PROP_TAG( PT_TSTRING, 0x0049) #define PR_ORIGINAL_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0049) #define PR_ORIGINAL_SUBJECT_A PROP_TAG( PT_STRING8, 0x0049) #define PR_DISC_VAL PROP_TAG( PT_BOOLEAN, 0x004A) #define PR_ORIG_MESSAGE_CLASS PROP_TAG( PT_TSTRING, 0x004B) #define PR_ORIG_MESSAGE_CLASS_W PROP_TAG( PT_UNICODE, 0x004B) #define PR_ORIG_MESSAGE_CLASS_A PROP_TAG( PT_STRING8, 0x004B) #define PR_ORIGINAL_AUTHOR_ENTRYID PROP_TAG( PT_BINARY, 0x004C) #define PR_ORIGINAL_AUTHOR_NAME PROP_TAG( PT_TSTRING, 0x004D) #define PR_ORIGINAL_AUTHOR_NAME_W PROP_TAG( PT_UNICODE, 0x004D) #define PR_ORIGINAL_AUTHOR_NAME_A PROP_TAG( PT_STRING8, 0x004D) #define PR_ORIGINAL_SUBMIT_TIME PROP_TAG( PT_SYSTIME, 0x004E) #define PR_REPLY_RECIPIENT_ENTRIES PROP_TAG( PT_BINARY, 0x004F) #define PR_REPLY_RECIPIENT_NAMES PROP_TAG( PT_TSTRING, 0x0050) #define PR_REPLY_RECIPIENT_NAMES_W PROP_TAG( PT_UNICODE, 0x0050) #define PR_REPLY_RECIPIENT_NAMES_A PROP_TAG( PT_STRING8, 0x0050) #define PR_RECEIVED_BY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0051) #define PR_RCVD_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0052) #define PR_READ_RECEIPT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0053) #define PR_REPORT_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0054) #define PR_ORIGINAL_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0055) #define PR_ORIGINAL_AUTHOR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0056) #define PR_MESSAGE_TO_ME PROP_TAG( PT_BOOLEAN, 0x0057) #define PR_MESSAGE_CC_ME PROP_TAG( PT_BOOLEAN, 0x0058) #define PR_MESSAGE_RECIP_ME PROP_TAG( PT_BOOLEAN, 0x0059) #define PR_ORIGINAL_SENDER_NAME PROP_TAG( PT_TSTRING, 0x005A) #define PR_ORIGINAL_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x005A) #define PR_ORIGINAL_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x005A) #define PR_ORIGINAL_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x005B) #define PR_ORIGINAL_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005C) #define PR_ORIGINAL_SENT_REPRESENTING_NAME PROP_TAG( PT_TSTRING, 0x005D) #define PR_ORIGINAL_SENT_REPRESENTING_NAME_W PROP_TAG( PT_UNICODE, 0x005D) #define PR_ORIGINAL_SENT_REPRESENTING_NAME_A PROP_TAG( PT_STRING8, 0x005D) #define PR_ORIGINAL_SENT_REPRESENTING_ENTRYID PROP_TAG( PT_BINARY, 0x005E) #define PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY PROP_TAG( PT_BINARY, 0x005F) #define PR_START_DATE PROP_TAG( PT_SYSTIME, 0x0060) #define PR_END_DATE PROP_TAG( PT_SYSTIME, 0x0061) #define PR_OWNER_APPT_ID PROP_TAG( PT_LONG, 0x0062) #define PR_RESPONSE_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0063) #define PR_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0064) #define PR_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0064) #define PR_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0064) #define PR_SENT_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0065) #define PR_SENT_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0065) #define PR_SENT_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0065) #define PR_ORIGINAL_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0066) #define PR_ORIGINAL_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0066) #define PR_ORIGINAL_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0066) #define PR_ORIGINAL_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0067) #define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0067) #define PR_ORIGINAL_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0067) #define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0068) #define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0068) #define PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0068) #define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS \ PROP_TAG( PT_TSTRING, 0x0069) #define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_W \ PROP_TAG( PT_UNICODE, 0x0069) #define PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS_A \ PROP_TAG( PT_STRING8, 0x0069) #define PR_CONVERSATION_TOPIC PROP_TAG( PT_TSTRING, 0x0070) #define PR_CONVERSATION_TOPIC_W PROP_TAG( PT_UNICODE, 0x0070) #define PR_CONVERSATION_TOPIC_A PROP_TAG( PT_STRING8, 0x0070) #define PR_CONVERSATION_INDEX PROP_TAG( PT_BINARY, 0x0071) #define PR_ORIGINAL_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0072) #define PR_ORIGINAL_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0072) #define PR_ORIGINAL_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0072) #define PR_ORIGINAL_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0073) #define PR_ORIGINAL_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0073) #define PR_ORIGINAL_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0073) #define PR_ORIGINAL_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0074) #define PR_ORIGINAL_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0074) #define PR_ORIGINAL_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0074) #define PR_RECEIVED_BY_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0075) #define PR_RECEIVED_BY_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0075) #define PR_RECEIVED_BY_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0075) #define PR_RECEIVED_BY_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0076) #define PR_RECEIVED_BY_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0076) #define PR_RECEIVED_BY_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0076) #define PR_RCVD_REPRESENTING_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0077) #define PR_RCVD_REPRESENTING_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0077) #define PR_RCVD_REPRESENTING_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0077) #define PR_RCVD_REPRESENTING_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0078) #define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0078) #define PR_RCVD_REPRESENTING_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0078) #define PR_ORIGINAL_AUTHOR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0079) #define PR_ORIGINAL_AUTHOR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0079) #define PR_ORIGINAL_AUTHOR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0079) #define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x007A) #define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x007A) #define PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x007A) #define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE PROP_TAG( PT_TSTRING, 0x007B) #define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x007B) #define PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x007B) #define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS \ PROP_TAG( PT_TSTRING, 0x007C) #define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_W \ PROP_TAG( PT_UNICODE, 0x007C) #define PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS_A \ PROP_TAG( PT_STRING8, 0x007C) #define PR_TRANSPORT_MESSAGE_HEADERS PROP_TAG(PT_TSTRING, 0x007D) #define PR_TRANSPORT_MESSAGE_HEADERS_W PROP_TAG(PT_UNICODE, 0x007D) #define PR_TRANSPORT_MESSAGE_HEADERS_A PROP_TAG(PT_STRING8, 0x007D) #define PR_DELEGATION PROP_TAG(PT_BINARY, 0x007E) #define PR_TNEF_CORRELATION_KEY PROP_TAG(PT_BINARY, 0x007F) #define PR_BODY PROP_TAG( PT_TSTRING, 0x1000) #define PR_BODY_W PROP_TAG( PT_UNICODE, 0x1000) #define PR_BODY_A PROP_TAG( PT_STRING8, 0x1000) #define PR_REPORT_TEXT PROP_TAG( PT_TSTRING, 0x1001) #define PR_REPORT_TEXT_W PROP_TAG( PT_UNICODE, 0x1001) #define PR_REPORT_TEXT_A PROP_TAG( PT_STRING8, 0x1001) #define PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY PROP_TAG( PT_BINARY, 0x1002) #define PR_REPORTING_DL_NAME PROP_TAG( PT_BINARY, 0x1003) #define PR_REPORTING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x1004) #define PR_RTF_SYNC_BODY_CRC PROP_TAG( PT_LONG, 0x1006) #define PR_RTF_SYNC_BODY_COUNT PROP_TAG( PT_LONG, 0x1007) #define PR_RTF_SYNC_BODY_TAG PROP_TAG( PT_TSTRING, 0x1008) #define PR_RTF_SYNC_BODY_TAG_W PROP_TAG( PT_UNICODE, 0x1008) #define PR_RTF_SYNC_BODY_TAG_A PROP_TAG( PT_STRING8, 0x1008) #define PR_RTF_COMPRESSED PROP_TAG( PT_BINARY, 0x1009) #define PR_RTF_SYNC_PREFIX_COUNT PROP_TAG( PT_LONG, 0x1010) #define PR_RTF_SYNC_TRAILING_COUNT PROP_TAG( PT_LONG, 0x1011) #define PR_ORIGINALLY_INTENDED_RECIP_ENTRYID PROP_TAG( PT_BINARY, 0x1012) #define PR_BODY_HTML PROP_TAG( PT_TSTRING, 0x1013) #define PR_BODY_HTML_W PROP_TAG( PT_UNICODE, 0x1013) #define PR_BODY_HTML_A PROP_TAG( PT_STRING8, 0x1013) #define PR_CONTENT_INTEGRITY_CHECK PROP_TAG( PT_BINARY, 0x0C00) #define PR_EXPLICIT_CONVERSION PROP_TAG( PT_LONG, 0x0C01) #define PR_IPM_RETURN_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C02) #define PR_MESSAGE_TOKEN PROP_TAG( PT_BINARY, 0x0C03) #define PR_NDR_REASON_CODE PROP_TAG( PT_LONG, 0x0C04) #define PR_NDR_DIAG_CODE PROP_TAG( PT_LONG, 0x0C05) #define PR_NON_RECEIPT_NOTIFICATION_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C06) #define PR_DELIVERY_POINT PROP_TAG( PT_LONG, 0x0C07) #define PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED \ PROP_TAG( PT_BOOLEAN, 0x0C08) #define PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT \ PROP_TAG( PT_BINARY, 0x0C09) #define PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY \ PROP_TAG( PT_BOOLEAN, 0x0C0A) #define PR_PHYSICAL_DELIVERY_MODE PROP_TAG( PT_LONG, 0x0C0B) #define PR_PHYSICAL_DELIVERY_REPORT_REQUEST PROP_TAG( PT_LONG, 0x0C0C) #define PR_PHYSICAL_FORWARDING_ADDRESS PROP_TAG( PT_BINARY, 0x0C0D) #define PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED \ PROP_TAG( PT_BOOLEAN, 0x0C0E) #define PR_PHYSICAL_FORWARDING_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x0C0F) #define PR_PHYSICAL_RENDITION_ATTRIBUTES PROP_TAG( PT_BINARY, 0x0C10) #define PR_PROOF_OF_DELIVERY PROP_TAG( PT_BINARY, 0x0C11) #define PR_PROOF_OF_DELIVERY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C12) #define PR_RECIPIENT_CERTIFICATE PROP_TAG( PT_BINARY, 0x0C13) #define PR_RECIPIENT_NUMBER_FOR_ADVICE PROP_TAG( PT_TSTRING, 0x0C14) #define PR_RECIPIENT_NUMBER_FOR_ADVICE_W PROP_TAG( PT_UNICODE, 0x0C14) #define PR_RECIPIENT_NUMBER_FOR_ADVICE_A PROP_TAG( PT_STRING8, 0x0C14) #define PR_RECIPIENT_TYPE PROP_TAG( PT_LONG, 0x0C15) #define PR_REGISTERED_MAIL_TYPE PROP_TAG( PT_LONG, 0x0C16) #define PR_REPLY_REQUESTED PROP_TAG( PT_BOOLEAN, 0x0C17) #define PR_REQUESTED_DELIVERY_METHOD PROP_TAG( PT_LONG, 0x0C18) #define PR_SENDER_ENTRYID PROP_TAG( PT_BINARY, 0x0C19) #define PR_SENDER_NAME PROP_TAG( PT_TSTRING, 0x0C1A) #define PR_SENDER_NAME_W PROP_TAG( PT_UNICODE, 0x0C1A) #define PR_SENDER_NAME_A PROP_TAG( PT_STRING8, 0x0C1A) #define PR_SUPPLEMENTARY_INFO PROP_TAG( PT_TSTRING, 0x0C1B) #define PR_SUPPLEMENTARY_INFO_W PROP_TAG( PT_UNICODE, 0x0C1B) #define PR_SUPPLEMENTARY_INFO_A PROP_TAG( PT_STRING8, 0x0C1B) #define PR_TYPE_OF_MTS_USER PROP_TAG( PT_LONG, 0x0C1C) #define PR_SENDER_SEARCH_KEY PROP_TAG( PT_BINARY, 0x0C1D) #define PR_SENDER_ADDRTYPE PROP_TAG( PT_TSTRING, 0x0C1E) #define PR_SENDER_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x0C1E) #define PR_SENDER_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x0C1E) #define PR_SENDER_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x0C1F) #define PR_SENDER_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x0C1F) #define PR_SENDER_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x0C1F) #define PR_CURRENT_VERSION PROP_TAG( PT_I8, 0x0E00) #define PR_DELETE_AFTER_SUBMIT PROP_TAG( PT_BOOLEAN, 0x0E01) #define PR_DISPLAY_BCC PROP_TAG( PT_TSTRING, 0x0E02) #define PR_DISPLAY_BCC_W PROP_TAG( PT_UNICODE, 0x0E02) #define PR_DISPLAY_BCC_A PROP_TAG( PT_STRING8, 0x0E02) #define PR_DISPLAY_CC PROP_TAG( PT_TSTRING, 0x0E03) #define PR_DISPLAY_CC_W PROP_TAG( PT_UNICODE, 0x0E03) #define PR_DISPLAY_CC_A PROP_TAG( PT_STRING8, 0x0E03) #define PR_DISPLAY_TO PROP_TAG( PT_TSTRING, 0x0E04) #define PR_DISPLAY_TO_W PROP_TAG( PT_UNICODE, 0x0E04) #define PR_DISPLAY_TO_A PROP_TAG( PT_STRING8, 0x0E04) #define PR_PARENT_DISPLAY PROP_TAG( PT_TSTRING, 0x0E05) #define PR_PARENT_DISPLAY_W PROP_TAG( PT_UNICODE, 0x0E05) #define PR_PARENT_DISPLAY_A PROP_TAG( PT_STRING8, 0x0E05) #define PR_MESSAGE_DELIVERY_TIME PROP_TAG( PT_SYSTIME, 0x0E06) #define PR_MESSAGE_FLAGS PROP_TAG( PT_LONG, 0x0E07) #define PR_MESSAGE_SIZE PROP_TAG( PT_LONG, 0x0E08) #define PR_PARENT_ENTRYID PROP_TAG( PT_BINARY, 0x0E09) #define PR_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x0E0A) #define PR_CORRELATE PROP_TAG( PT_BOOLEAN, 0x0E0C) #define PR_CORRELATE_MTSID PROP_TAG( PT_BINARY, 0x0E0D) #define PR_DISCRETE_VALUES PROP_TAG( PT_BOOLEAN, 0x0E0E) #define PR_RESPONSIBILITY PROP_TAG( PT_BOOLEAN, 0x0E0F) #define PR_SPOOLER_STATUS PROP_TAG( PT_LONG, 0x0E10) #define PR_TRANSPORT_STATUS PROP_TAG( PT_LONG, 0x0E11) #define PR_MESSAGE_RECIPIENTS PROP_TAG( PT_OBJECT, 0x0E12) #define PR_MESSAGE_ATTACHMENTS PROP_TAG( PT_OBJECT, 0x0E13) #define PR_SUBMIT_FLAGS PROP_TAG( PT_LONG, 0x0E14) #define PR_RECIPIENT_STATUS PROP_TAG( PT_LONG, 0x0E15) #define PR_TRANSPORT_KEY PROP_TAG( PT_LONG, 0x0E16) #define PR_MSG_STATUS PROP_TAG( PT_LONG, 0x0E17) #define PR_MESSAGE_DOWNLOAD_TIME PROP_TAG( PT_LONG, 0x0E18) #define PR_CREATION_VERSION PROP_TAG( PT_I8, 0x0E19) #define PR_MODIFY_VERSION PROP_TAG( PT_I8, 0x0E1A) #define PR_HASATTACH PROP_TAG( PT_BOOLEAN, 0x0E1B) #define PR_BODY_CRC PROP_TAG( PT_LONG, 0x0E1C) #define PR_NORMALIZED_SUBJECT PROP_TAG( PT_TSTRING, 0x0E1D) #define PR_NORMALIZED_SUBJECT_W PROP_TAG( PT_UNICODE, 0x0E1D) #define PR_NORMALIZED_SUBJECT_A PROP_TAG( PT_STRING8, 0x0E1D) #define PR_RTF_IN_SYNC PROP_TAG( PT_BOOLEAN, 0x0E1F) #define PR_ATTACH_SIZE PROP_TAG( PT_LONG, 0x0E20) #define PR_ATTACH_NUM PROP_TAG( PT_LONG, 0x0E21) #define PR_PREPROCESS PROP_TAG( PT_BOOLEAN, 0x0E22) #define PR_ORIGINATING_MTA_CERTIFICATE PROP_TAG( PT_BINARY, 0x0E25) #define PR_PROOF_OF_SUBMISSION PROP_TAG( PT_BINARY, 0x0E26) #define PR_PRIMARY_SEND_ACCT PROP_TAG( PT_UNICODE, 0x0E28) #define PR_ENTRYID PROP_TAG( PT_BINARY, 0x0FFF) #define PR_OBJECT_TYPE PROP_TAG( PT_LONG, 0x0FFE) #define PR_ICON PROP_TAG( PT_BINARY, 0x0FFD) #define PR_MINI_ICON PROP_TAG( PT_BINARY, 0x0FFC) #define PR_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x0FFB) #define PR_STORE_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FFA) #define PR_RECORD_KEY PROP_TAG( PT_BINARY, 0x0FF9) #define PR_MAPPING_SIGNATURE PROP_TAG( PT_BINARY, 0x0FF8) #define PR_ACCESS_LEVEL PROP_TAG( PT_LONG, 0x0FF7) #define PR_INSTANCE_KEY PROP_TAG( PT_BINARY, 0x0FF6) #define PR_ROW_TYPE PROP_TAG( PT_LONG, 0x0FF5) #define PR_ACCESS PROP_TAG( PT_LONG, 0x0FF4) #define PR_ROWID PROP_TAG( PT_LONG, 0x3000) #define PR_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3001) #define PR_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3001) #define PR_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3001) #define PR_ADDRTYPE PROP_TAG( PT_TSTRING, 0x3002) #define PR_ADDRTYPE_W PROP_TAG( PT_UNICODE, 0x3002) #define PR_ADDRTYPE_A PROP_TAG( PT_STRING8, 0x3002) #define PR_EMAIL_ADDRESS PROP_TAG( PT_TSTRING, 0x3003) #define PR_EMAIL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3003) #define PR_EMAIL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3003) #define PR_COMMENT PROP_TAG( PT_TSTRING, 0x3004) #define PR_COMMENT_W PROP_TAG( PT_UNICODE, 0x3004) #define PR_COMMENT_A PROP_TAG( PT_STRING8, 0x3004) #define PR_DEPTH PROP_TAG( PT_LONG, 0x3005) #define PR_PROVIDER_DISPLAY PROP_TAG( PT_TSTRING, 0x3006) #define PR_PROVIDER_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3006) #define PR_PROVIDER_DISPLAY_A PROP_TAG( PT_STRING8, 0x3006) #define PR_CREATION_TIME PROP_TAG( PT_SYSTIME, 0x3007) #define PR_LAST_MODIFICATION_TIME PROP_TAG( PT_SYSTIME, 0x3008) #define PR_RESOURCE_FLAGS PROP_TAG( PT_LONG, 0x3009) #define PR_PROVIDER_DLL_NAME PROP_TAG( PT_TSTRING, 0x300A) #define PR_PROVIDER_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x300A) #define PR_PROVIDER_DLL_NAME_A PROP_TAG( PT_STRING8, 0x300A) #define PR_SEARCH_KEY PROP_TAG( PT_BINARY, 0x300B) #define PR_PROVIDER_UID PROP_TAG( PT_BINARY, 0x300C) #define PR_PROVIDER_ORDINAL PROP_TAG( PT_LONG, 0x300D) #define PR_FORM_VERSION PROP_TAG(PT_TSTRING, 0x3301) #define PR_FORM_VERSION_W PROP_TAG(PT_UNICODE, 0x3301) #define PR_FORM_VERSION_A PROP_TAG(PT_STRING8, 0x3301) #define PR_FORM_CLSID PROP_TAG(PT_CLSID, 0x3302) #define PR_FORM_CONTACT_NAME PROP_TAG(PT_TSTRING, 0x3303) #define PR_FORM_CONTACT_NAME_W PROP_TAG(PT_UNICODE, 0x3303) #define PR_FORM_CONTACT_NAME_A PROP_TAG(PT_STRING8, 0x3303) #define PR_FORM_CATEGORY PROP_TAG(PT_TSTRING, 0x3304) #define PR_FORM_CATEGORY_W PROP_TAG(PT_UNICODE, 0x3304) #define PR_FORM_CATEGORY_A PROP_TAG(PT_STRING8, 0x3304) #define PR_FORM_CATEGORY_SUB PROP_TAG(PT_TSTRING, 0x3305) #define PR_FORM_CATEGORY_SUB_W PROP_TAG(PT_UNICODE, 0x3305) #define PR_FORM_CATEGORY_SUB_A PROP_TAG(PT_STRING8, 0x3305) #define PR_FORM_HOST_MAP PROP_TAG(PT_MV_LONG, 0x3306) #define PR_FORM_HIDDEN PROP_TAG(PT_BOOLEAN, 0x3307) #define PR_FORM_DESIGNER_NAME PROP_TAG(PT_TSTRING, 0x3308) #define PR_FORM_DESIGNER_NAME_W PROP_TAG(PT_UNICODE, 0x3308) #define PR_FORM_DESIGNER_NAME_A PROP_TAG(PT_STRING8, 0x3308) #define PR_FORM_DESIGNER_GUID PROP_TAG(PT_CLSID, 0x3309) #define PR_FORM_MESSAGE_BEHAVIOR PROP_TAG(PT_LONG, 0x330A) #define PR_DEFAULT_STORE PROP_TAG( PT_BOOLEAN, 0x3400) #define PR_STORE_SUPPORT_MASK PROP_TAG( PT_LONG, 0x340D) #define PR_STORE_STATE PROP_TAG( PT_LONG, 0x340E) #define PR_IPM_SUBTREE_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3410) #define PR_IPM_OUTBOX_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3411) #define PR_IPM_WASTEBASKET_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3412) #define PR_IPM_SENTMAIL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3413) #define PR_MDB_PROVIDER PROP_TAG( PT_BINARY, 0x3414) #define PR_RECEIVE_FOLDER_SETTINGS PROP_TAG( PT_OBJECT, 0x3415) #define PR_VALID_FOLDER_MASK PROP_TAG( PT_LONG, 0x35DF) #define PR_IPM_SUBTREE_ENTRYID PROP_TAG( PT_BINARY, 0x35E0) #define PR_IPM_OUTBOX_ENTRYID PROP_TAG( PT_BINARY, 0x35E2) #define PR_IPM_WASTEBASKET_ENTRYID PROP_TAG( PT_BINARY, 0x35E3) #define PR_IPM_SENTMAIL_ENTRYID PROP_TAG( PT_BINARY, 0x35E4) #define PR_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E5) #define PR_COMMON_VIEWS_ENTRYID PROP_TAG( PT_BINARY, 0x35E6) #define PR_FINDER_ENTRYID PROP_TAG( PT_BINARY, 0x35E7) #define PR_CONTAINER_FLAGS PROP_TAG( PT_LONG, 0x3600) #define PR_FOLDER_TYPE PROP_TAG( PT_LONG, 0x3601) #define PR_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3602) #define PR_CONTENT_UNREAD PROP_TAG( PT_LONG, 0x3603) #define PR_CREATE_TEMPLATES PROP_TAG( PT_OBJECT, 0x3604) #define PR_DETAILS_TABLE PROP_TAG( PT_OBJECT, 0x3605) #define PR_SEARCH PROP_TAG( PT_OBJECT, 0x3607) #define PR_SELECTABLE PROP_TAG( PT_BOOLEAN, 0x3609) #define PR_SUBFOLDERS PROP_TAG( PT_BOOLEAN, 0x360A) #define PR_STATUS PROP_TAG( PT_LONG, 0x360B) #define PR_ANR PROP_TAG( PT_TSTRING, 0x360C) #define PR_ANR_W PROP_TAG( PT_UNICODE, 0x360C) #define PR_ANR_A PROP_TAG( PT_STRING8, 0x360C) #define PR_CONTENTS_SORT_ORDER PROP_TAG( PT_MV_LONG, 0x360D) #define PR_CONTAINER_HIERARCHY PROP_TAG( PT_OBJECT, 0x360E) #define PR_CONTAINER_CONTENTS PROP_TAG( PT_OBJECT, 0x360F) #define PR_FOLDER_ASSOCIATED_CONTENTS PROP_TAG( PT_OBJECT, 0x3610) #define PR_DEF_CREATE_DL PROP_TAG( PT_BINARY, 0x3611) #define PR_DEF_CREATE_MAILUSER PROP_TAG( PT_BINARY, 0x3612) #define PR_CONTAINER_CLASS PROP_TAG( PT_TSTRING, 0x3613) #define PR_CONTAINER_CLASS_W PROP_TAG( PT_UNICODE, 0x3613) #define PR_CONTAINER_CLASS_A PROP_TAG( PT_STRING8, 0x3613) #define PR_CONTAINER_MODIFY_VERSION PROP_TAG( PT_I8, 0x3614) #define PR_AB_PROVIDER_ID PROP_TAG( PT_BINARY, 0x3615) #define PR_DEFAULT_VIEW_ENTRYID PROP_TAG( PT_BINARY, 0x3616) #define PR_ASSOC_CONTENT_COUNT PROP_TAG( PT_LONG, 0x3617) #define PR_ATTACHMENT_X400_PARAMETERS PROP_TAG( PT_BINARY, 0x3700) #define PR_ATTACH_DATA_OBJ PROP_TAG( PT_OBJECT, 0x3701) #define PR_ATTACH_DATA_BIN PROP_TAG( PT_BINARY, 0x3701) #define PR_ATTACH_ENCODING PROP_TAG( PT_BINARY, 0x3702) #define PR_ATTACH_EXTENSION PROP_TAG( PT_TSTRING, 0x3703) #define PR_ATTACH_EXTENSION_W PROP_TAG( PT_UNICODE, 0x3703) #define PR_ATTACH_EXTENSION_A PROP_TAG( PT_STRING8, 0x3703) #define PR_ATTACH_FILENAME PROP_TAG( PT_TSTRING, 0x3704) #define PR_ATTACH_FILENAME_W PROP_TAG( PT_UNICODE, 0x3704) #define PR_ATTACH_FILENAME_A PROP_TAG( PT_STRING8, 0x3704) #define PR_ATTACH_METHOD PROP_TAG( PT_LONG, 0x3705) #define PR_ATTACH_LONG_FILENAME PROP_TAG( PT_TSTRING, 0x3707) #define PR_ATTACH_LONG_FILENAME_W PROP_TAG( PT_UNICODE, 0x3707) #define PR_ATTACH_LONG_FILENAME_A PROP_TAG( PT_STRING8, 0x3707) #define PR_ATTACH_PATHNAME PROP_TAG( PT_TSTRING, 0x3708) #define PR_ATTACH_PATHNAME_W PROP_TAG( PT_UNICODE, 0x3708) #define PR_ATTACH_PATHNAME_A PROP_TAG( PT_STRING8, 0x3708) #define PR_ATTACH_RENDERING PROP_TAG( PT_BINARY, 0x3709) #define PR_ATTACH_TAG PROP_TAG( PT_BINARY, 0x370A) #define PR_RENDERING_POSITION PROP_TAG( PT_LONG, 0x370B) #define PR_ATTACH_TRANSPORT_NAME PROP_TAG( PT_TSTRING, 0x370C) #define PR_ATTACH_TRANSPORT_NAME_W PROP_TAG( PT_UNICODE, 0x370C) #define PR_ATTACH_TRANSPORT_NAME_A PROP_TAG( PT_STRING8, 0x370C) #define PR_ATTACH_LONG_PATHNAME PROP_TAG( PT_TSTRING, 0x370D) #define PR_ATTACH_LONG_PATHNAME_W PROP_TAG( PT_UNICODE, 0x370D) #define PR_ATTACH_LONG_PATHNAME_A PROP_TAG( PT_STRING8, 0x370D) #define PR_ATTACH_MIME_TAG PROP_TAG( PT_TSTRING, 0x370E) #define PR_ATTACH_MIME_TAG_W PROP_TAG( PT_UNICODE, 0x370E) #define PR_ATTACH_MIME_TAG_A PROP_TAG( PT_STRING8, 0x370E) #define PR_ATTACH_ADDITIONAL_INFO PROP_TAG( PT_BINARY, 0x370F) #define PR_ATTACH_CONTENT_ID PROP_TAG( PT_UNICODE, 0x3712) #define PR_DISPLAY_TYPE PROP_TAG( PT_LONG, 0x3900) #define PR_TEMPLATEID PROP_TAG( PT_BINARY, 0x3902) #define PR_PRIMARY_CAPABILITY PROP_TAG( PT_BINARY, 0x3904) #define PR_7BIT_DISPLAY_NAME PROP_TAG( PT_STRING8, 0x39FF) #define PR_ACCOUNT PROP_TAG( PT_TSTRING, 0x3A00) #define PR_ACCOUNT_W PROP_TAG( PT_UNICODE, 0x3A00) #define PR_ACCOUNT_A PROP_TAG( PT_STRING8, 0x3A00) #define PR_ALTERNATE_RECIPIENT PROP_TAG( PT_BINARY, 0x3A01) #define PR_CALLBACK_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A02) #define PR_CALLBACK_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A02) #define PR_CALLBACK_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A02) #define PR_CONVERSION_PROHIBITED PROP_TAG( PT_BOOLEAN, 0x3A03) #define PR_DISCLOSE_RECIPIENTS PROP_TAG( PT_BOOLEAN, 0x3A04) #define PR_GENERATION PROP_TAG( PT_TSTRING, 0x3A05) #define PR_GENERATION_W PROP_TAG( PT_UNICODE, 0x3A05) #define PR_GENERATION_A PROP_TAG( PT_STRING8, 0x3A05) #define PR_GIVEN_NAME PROP_TAG( PT_TSTRING, 0x3A06) #define PR_GIVEN_NAME_W PROP_TAG( PT_UNICODE, 0x3A06) #define PR_GIVEN_NAME_A PROP_TAG( PT_STRING8, 0x3A06) #define PR_GOVERNMENT_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A07) #define PR_GOVERNMENT_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A07) #define PR_GOVERNMENT_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A07) #define PR_BUSINESS_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A08) #define PR_BUSINESS_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A08) #define PR_BUSINESS_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A08) #define PR_OFFICE_TELEPHONE_NUMBER PR_BUSINESS_TELEPHONE_NUMBER #define PR_OFFICE_TELEPHONE_NUMBER_W PR_BUSINESS_TELEPHONE_NUMBER_W #define PR_OFFICE_TELEPHONE_NUMBER_A PR_BUSINESS_TELEPHONE_NUMBER_A #define PR_HOME_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A09) #define PR_HOME_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A09) #define PR_HOME_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A09) #define PR_INITIALS PROP_TAG( PT_TSTRING, 0x3A0A) #define PR_INITIALS_W PROP_TAG( PT_UNICODE, 0x3A0A) #define PR_INITIALS_A PROP_TAG( PT_STRING8, 0x3A0A) #define PR_KEYWORD PROP_TAG( PT_TSTRING, 0x3A0B) #define PR_KEYWORD_W PROP_TAG( PT_UNICODE, 0x3A0B) #define PR_KEYWORD_A PROP_TAG( PT_STRING8, 0x3A0B) #define PR_LANGUAGE PROP_TAG( PT_TSTRING, 0x3A0C) #define PR_LANGUAGE_W PROP_TAG( PT_UNICODE, 0x3A0C) #define PR_LANGUAGE_A PROP_TAG( PT_STRING8, 0x3A0C) #define PR_LOCATION PROP_TAG( PT_TSTRING, 0x3A0D) #define PR_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A0D) #define PR_LOCATION_A PROP_TAG( PT_STRING8, 0x3A0D) #define PR_MAIL_PERMISSION PROP_TAG( PT_BOOLEAN, 0x3A0E) #define PR_MHS_COMMON_NAME PROP_TAG( PT_TSTRING, 0x3A0F) #define PR_MHS_COMMON_NAME_W PROP_TAG( PT_UNICODE, 0x3A0F) #define PR_MHS_COMMON_NAME_A PROP_TAG( PT_STRING8, 0x3A0F) #define PR_ORGANIZATIONAL_ID_NUMBER PROP_TAG( PT_TSTRING, 0x3A10) #define PR_ORGANIZATIONAL_ID_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A10) #define PR_ORGANIZATIONAL_ID_NUMBER_A PROP_TAG( PT_STRING8, 0x3A10) #define PR_SURNAME PROP_TAG( PT_TSTRING, 0x3A11) #define PR_SURNAME_W PROP_TAG( PT_UNICODE, 0x3A11) #define PR_SURNAME_A PROP_TAG( PT_STRING8, 0x3A11) #define PR_ORIGINAL_ENTRYID PROP_TAG( PT_BINARY, 0x3A12) #define PR_ORIGINAL_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A13) #define PR_ORIGINAL_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A13) #define PR_ORIGINAL_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A13) #define PR_ORIGINAL_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3A14) #define PR_POSTAL_ADDRESS PROP_TAG( PT_TSTRING, 0x3A15) #define PR_POSTAL_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A15) #define PR_POSTAL_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A15) #define PR_COMPANY_NAME PROP_TAG( PT_TSTRING, 0x3A16) #define PR_COMPANY_NAME_W PROP_TAG( PT_UNICODE, 0x3A16) #define PR_COMPANY_NAME_A PROP_TAG( PT_STRING8, 0x3A16) #define PR_TITLE PROP_TAG( PT_TSTRING, 0x3A17) #define PR_TITLE_W PROP_TAG( PT_UNICODE, 0x3A17) #define PR_TITLE_A PROP_TAG( PT_STRING8, 0x3A17) #define PR_DEPARTMENT_NAME PROP_TAG( PT_TSTRING, 0x3A18) #define PR_DEPARTMENT_NAME_W PROP_TAG( PT_UNICODE, 0x3A18) #define PR_DEPARTMENT_NAME_A PROP_TAG( PT_STRING8, 0x3A18) #define PR_OFFICE_LOCATION PROP_TAG( PT_TSTRING, 0x3A19) #define PR_OFFICE_LOCATION_W PROP_TAG( PT_UNICODE, 0x3A19) #define PR_OFFICE_LOCATION_A PROP_TAG( PT_STRING8, 0x3A19) #define PR_PRIMARY_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1A) #define PR_PRIMARY_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1A) #define PR_PRIMARY_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1A) #define PR_BUSINESS2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1B) #define PR_BUSINESS2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1B) #define PR_BUSINESS2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1B) #define PR_OFFICE2_TELEPHONE_NUMBER PR_BUSINESS2_TELEPHONE_NUMBER #define PR_OFFICE2_TELEPHONE_NUMBER_W PR_BUSINESS2_TELEPHONE_NUMBER_W #define PR_OFFICE2_TELEPHONE_NUMBER_A PR_BUSINESS2_TELEPHONE_NUMBER_A #define PR_MOBILE_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1C) #define PR_MOBILE_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1C) #define PR_MOBILE_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1C) #define PR_CELLULAR_TELEPHONE_NUMBER PR_MOBILE_TELEPHONE_NUMBER #define PR_CELLULAR_TELEPHONE_NUMBER_W PR_MOBILE_TELEPHONE_NUMBER_W #define PR_CELLULAR_TELEPHONE_NUMBER_A PR_MOBILE_TELEPHONE_NUMBER_A #define PR_RADIO_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1D) #define PR_RADIO_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1D) #define PR_RADIO_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1D) #define PR_CAR_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1E) #define PR_CAR_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1E) #define PR_CAR_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1E) #define PR_OTHER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A1F) #define PR_OTHER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A1F) #define PR_OTHER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A1F) #define PR_TRANSMITABLE_DISPLAY_NAME PROP_TAG( PT_TSTRING, 0x3A20) #define PR_TRANSMITABLE_DISPLAY_NAME_W PROP_TAG( PT_UNICODE, 0x3A20) #define PR_TRANSMITABLE_DISPLAY_NAME_A PROP_TAG( PT_STRING8, 0x3A20) #define PR_PAGER_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A21) #define PR_PAGER_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A21) #define PR_PAGER_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A21) #define PR_BEEPER_TELEPHONE_NUMBER PR_PAGER_TELEPHONE_NUMBER #define PR_BEEPER_TELEPHONE_NUMBER_W PR_PAGER_TELEPHONE_NUMBER_W #define PR_BEEPER_TELEPHONE_NUMBER_A PR_PAGER_TELEPHONE_NUMBER_A #define PR_USER_CERTIFICATE PROP_TAG( PT_BINARY, 0x3A22) #define PR_PRIMARY_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A23) #define PR_PRIMARY_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A23) #define PR_PRIMARY_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A23) #define PR_BUSINESS_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A24) #define PR_BUSINESS_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A24) #define PR_BUSINESS_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A24) #define PR_HOME_FAX_NUMBER PROP_TAG( PT_TSTRING, 0x3A25) #define PR_HOME_FAX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A25) #define PR_HOME_FAX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A25) #define PR_COUNTRY PROP_TAG( PT_TSTRING, 0x3A26) #define PR_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A26) #define PR_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A26) #define PR_BUSINESS_ADDRESS_COUNTRY PR_COUNTRY #define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W #define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A #define PR_LOCALITY PROP_TAG( PT_TSTRING, 0x3A27) #define PR_LOCALITY_W PROP_TAG( PT_UNICODE, 0x3A27) #define PR_LOCALITY_A PROP_TAG( PT_STRING8, 0x3A27) #define PR_BUSINESS_ADDRESS_CITY PR_LOCALITY #define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W #define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A #define PR_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A28) #define PR_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A28) #define PR_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A28) #define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE PR_STATE_OR_PROVINCE #define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W #define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A #define PR_STREET_ADDRESS PROP_TAG( PT_TSTRING, 0x3A29) #define PR_STREET_ADDRESS_W PROP_TAG( PT_UNICODE, 0x3A29) #define PR_STREET_ADDRESS_A PROP_TAG( PT_STRING8, 0x3A29) #define PR_BUSINESS_ADDRESS_STREET PR_STREET_ADDRESS #define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W #define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A #define PR_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A2A) #define PR_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A2A) #define PR_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A2A) #define PR_BUSINESS_ADDRESS_POSTAL_CODE PR_POSTAL_CODE #define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W #define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A #define PR_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A2B) #define PR_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A2B) #define PR_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A2B) #define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX PR_POST_OFFICE_BOX #define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_W PR_POST_OFFICE_BOX_W #define PR_BUSINESS_ADDRESS_POST_OFFICE_BOX_A PR_POST_OFFICE_BOX_A #define PR_TELEX_NUMBER PROP_TAG( PT_TSTRING, 0x3A2C) #define PR_TELEX_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2C) #define PR_TELEX_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2C) #define PR_ISDN_NUMBER PROP_TAG( PT_TSTRING, 0x3A2D) #define PR_ISDN_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2D) #define PR_ISDN_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2D) #define PR_ASSISTANT_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2E) #define PR_ASSISTANT_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2E) #define PR_ASSISTANT_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2E) #define PR_HOME2_TELEPHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A2F) #define PR_HOME2_TELEPHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A2F) #define PR_HOME2_TELEPHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A2F) #define PR_ASSISTANT PROP_TAG( PT_TSTRING, 0x3A30) #define PR_ASSISTANT_W PROP_TAG( PT_UNICODE, 0x3A30) #define PR_ASSISTANT_A PROP_TAG( PT_STRING8, 0x3A30) #define PR_SEND_RICH_INFO PROP_TAG( PT_BOOLEAN, 0x3A40) #define PR_WEDDING_ANNIVERSARY PROP_TAG( PT_SYSTIME, 0x3A41) #define PR_BIRTHDAY PROP_TAG( PT_SYSTIME, 0x3A42) #define PR_HOBBIES PROP_TAG( PT_TSTRING, 0x3A43) #define PR_HOBBIES_W PROP_TAG( PT_UNICODE, 0x3A43) #define PR_HOBBIES_A PROP_TAG( PT_STRING8, 0x3A43) #define PR_MIDDLE_NAME PROP_TAG( PT_TSTRING, 0x3A44) #define PR_MIDDLE_NAME_W PROP_TAG( PT_UNICODE, 0x3A44) #define PR_MIDDLE_NAME_A PROP_TAG( PT_STRING8, 0x3A44) #define PR_DISPLAY_NAME_PREFIX PROP_TAG( PT_TSTRING, 0x3A45) #define PR_DISPLAY_NAME_PREFIX_W PROP_TAG( PT_UNICODE, 0x3A45) #define PR_DISPLAY_NAME_PREFIX_A PROP_TAG( PT_STRING8, 0x3A45) #define PR_PROFESSION PROP_TAG( PT_TSTRING, 0x3A46) #define PR_PROFESSION_W PROP_TAG( PT_UNICODE, 0x3A46) #define PR_PROFESSION_A PROP_TAG( PT_STRING8, 0x3A46) #define PR_PREFERRED_BY_NAME PROP_TAG( PT_TSTRING, 0x3A47) #define PR_PREFERRED_BY_NAME_W PROP_TAG( PT_UNICODE, 0x3A47) #define PR_PREFERRED_BY_NAME_A PROP_TAG( PT_STRING8, 0x3A47) #define PR_SPOUSE_NAME PROP_TAG( PT_TSTRING, 0x3A48) #define PR_SPOUSE_NAME_W PROP_TAG( PT_UNICODE, 0x3A48) #define PR_SPOUSE_NAME_A PROP_TAG( PT_STRING8, 0x3A48) #define PR_COMPUTER_NETWORK_NAME PROP_TAG( PT_TSTRING, 0x3A49) #define PR_COMPUTER_NETWORK_NAME_W PROP_TAG( PT_UNICODE, 0x3A49) #define PR_COMPUTER_NETWORK_NAME_A PROP_TAG( PT_STRING8, 0x3A49) #define PR_CUSTOMER_ID PROP_TAG( PT_TSTRING, 0x3A4A) #define PR_CUSTOMER_ID_W PROP_TAG( PT_UNICODE, 0x3A4A) #define PR_CUSTOMER_ID_A PROP_TAG( PT_STRING8, 0x3A4A) #define PR_TTYTDD_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A4B) #define PR_TTYTDD_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A4B) #define PR_TTYTDD_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A4B) #define PR_FTP_SITE PROP_TAG( PT_TSTRING, 0x3A4C) #define PR_FTP_SITE_W PROP_TAG( PT_UNICODE, 0x3A4C) #define PR_FTP_SITE_A PROP_TAG( PT_STRING8, 0x3A4C) #define PR_GENDER PROP_TAG( PT_SHORT, 0x3A4D) #define PR_MANAGER_NAME PROP_TAG( PT_TSTRING, 0x3A4E) #define PR_MANAGER_NAME_W PROP_TAG( PT_UNICODE, 0x3A4E) #define PR_MANAGER_NAME_A PROP_TAG( PT_STRING8, 0x3A4E) #define PR_NICKNAME PROP_TAG( PT_TSTRING, 0x3A4F) #define PR_NICKNAME_W PROP_TAG( PT_UNICODE, 0x3A4F) #define PR_NICKNAME_A PROP_TAG( PT_STRING8, 0x3A4F) #define PR_PERSONAL_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A50) #define PR_PERSONAL_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A50) #define PR_PERSONAL_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A50) #define PR_BUSINESS_HOME_PAGE PROP_TAG( PT_TSTRING, 0x3A51) #define PR_BUSINESS_HOME_PAGE_W PROP_TAG( PT_UNICODE, 0x3A51) #define PR_BUSINESS_HOME_PAGE_A PROP_TAG( PT_STRING8, 0x3A51) #define PR_CONTACT_VERSION PROP_TAG( PT_CLSID, 0x3A52) #define PR_CONTACT_ENTRYIDS PROP_TAG( PT_MV_BINARY, 0x3A53) #define PR_CONTACT_ADDRTYPES PROP_TAG( PT_MV_TSTRING, 0x3A54) #define PR_CONTACT_ADDRTYPES_W PROP_TAG( PT_MV_UNICODE, 0x3A54) #define PR_CONTACT_ADDRTYPES_A PROP_TAG( PT_MV_STRING8, 0x3A54) #define PR_CONTACT_DEFAULT_ADDRESS_INDEX PROP_TAG( PT_LONG, 0x3A55) #define PR_CONTACT_EMAIL_ADDRESSES PROP_TAG( PT_MV_TSTRING, 0x3A56) #define PR_CONTACT_EMAIL_ADDRESSES_W PROP_TAG( PT_MV_UNICODE, 0x3A56) #define PR_CONTACT_EMAIL_ADDRESSES_A PROP_TAG( PT_MV_STRING8, 0x3A56) #define PR_COMPANY_MAIN_PHONE_NUMBER PROP_TAG( PT_TSTRING, 0x3A57) #define PR_COMPANY_MAIN_PHONE_NUMBER_W PROP_TAG( PT_UNICODE, 0x3A57) #define PR_COMPANY_MAIN_PHONE_NUMBER_A PROP_TAG( PT_STRING8, 0x3A57) #define PR_CHILDRENS_NAMES PROP_TAG( PT_MV_TSTRING, 0x3A58) #define PR_CHILDRENS_NAMES_W PROP_TAG( PT_MV_UNICODE, 0x3A58) #define PR_CHILDRENS_NAMES_A PROP_TAG( PT_MV_STRING8, 0x3A58) #define PR_HOME_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A59) #define PR_HOME_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A59) #define PR_HOME_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A59) #define PR_HOME_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A5A) #define PR_HOME_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A5A) #define PR_HOME_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A5A) #define PR_HOME_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A5B) #define PR_HOME_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A5B) #define PR_HOME_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A5B) #define PR_HOME_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A5C) #define PR_HOME_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A5C) #define PR_HOME_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A5C) #define PR_HOME_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A5D) #define PR_HOME_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A5D) #define PR_HOME_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A5D) #define PR_HOME_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A5E) #define PR_HOME_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A5E) #define PR_HOME_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A5E) #define PR_OTHER_ADDRESS_CITY PROP_TAG( PT_TSTRING, 0x3A5F) #define PR_OTHER_ADDRESS_CITY_W PROP_TAG( PT_UNICODE, 0x3A5F) #define PR_OTHER_ADDRESS_CITY_A PROP_TAG( PT_STRING8, 0x3A5F) #define PR_OTHER_ADDRESS_COUNTRY PROP_TAG( PT_TSTRING, 0x3A60) #define PR_OTHER_ADDRESS_COUNTRY_W PROP_TAG( PT_UNICODE, 0x3A60) #define PR_OTHER_ADDRESS_COUNTRY_A PROP_TAG( PT_STRING8, 0x3A60) #define PR_OTHER_ADDRESS_POSTAL_CODE PROP_TAG( PT_TSTRING, 0x3A61) #define PR_OTHER_ADDRESS_POSTAL_CODE_W PROP_TAG( PT_UNICODE, 0x3A61) #define PR_OTHER_ADDRESS_POSTAL_CODE_A PROP_TAG( PT_STRING8, 0x3A61) #define PR_OTHER_ADDRESS_STATE_OR_PROVINCE PROP_TAG( PT_TSTRING, 0x3A62) #define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_W PROP_TAG( PT_UNICODE, 0x3A62) #define PR_OTHER_ADDRESS_STATE_OR_PROVINCE_A PROP_TAG( PT_STRING8, 0x3A62) #define PR_OTHER_ADDRESS_STREET PROP_TAG( PT_TSTRING, 0x3A63) #define PR_OTHER_ADDRESS_STREET_W PROP_TAG( PT_UNICODE, 0x3A63) #define PR_OTHER_ADDRESS_STREET_A PROP_TAG( PT_STRING8, 0x3A63) #define PR_OTHER_ADDRESS_POST_OFFICE_BOX PROP_TAG( PT_TSTRING, 0x3A64) #define PR_OTHER_ADDRESS_POST_OFFICE_BOX_W PROP_TAG( PT_UNICODE, 0x3A64) #define PR_OTHER_ADDRESS_POST_OFFICE_BOX_A PROP_TAG( PT_STRING8, 0x3A64) #define PR_STORE_PROVIDERS PROP_TAG( PT_BINARY, 0x3D00) #define PR_AB_PROVIDERS PROP_TAG( PT_BINARY, 0x3D01) #define PR_TRANSPORT_PROVIDERS PROP_TAG( PT_BINARY, 0x3D02) #define PR_DEFAULT_PROFILE PROP_TAG( PT_BOOLEAN, 0x3D04) #define PR_AB_SEARCH_PATH PROP_TAG( PT_MV_BINARY, 0x3D05) #define PR_AB_DEFAULT_DIR PROP_TAG( PT_BINARY, 0x3D06) #define PR_AB_DEFAULT_PAB PROP_TAG( PT_BINARY, 0x3D07) #define PR_FILTERING_HOOKS PROP_TAG( PT_BINARY, 0x3D08) #define PR_SERVICE_NAME PROP_TAG( PT_TSTRING, 0x3D09) #define PR_SERVICE_NAME_W PROP_TAG( PT_UNICODE, 0x3D09) #define PR_SERVICE_NAME_A PROP_TAG( PT_STRING8, 0x3D09) #define PR_SERVICE_DLL_NAME PROP_TAG( PT_TSTRING, 0x3D0A) #define PR_SERVICE_DLL_NAME_W PROP_TAG( PT_UNICODE, 0x3D0A) #define PR_SERVICE_DLL_NAME_A PROP_TAG( PT_STRING8, 0x3D0A) #define PR_SERVICE_ENTRY_NAME PROP_TAG( PT_STRING8, 0x3D0B) #define PR_SERVICE_UID PROP_TAG( PT_BINARY, 0x3D0C) #define PR_SERVICE_EXTRA_UIDS PROP_TAG( PT_BINARY, 0x3D0D) #define PR_SERVICES PROP_TAG( PT_BINARY, 0x3D0E) #define PR_SERVICE_SUPPORT_FILES PROP_TAG( PT_MV_TSTRING, 0x3D0F) #define PR_SERVICE_SUPPORT_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D0F) #define PR_SERVICE_SUPPORT_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D0F) #define PR_SERVICE_DELETE_FILES PROP_TAG( PT_MV_TSTRING, 0x3D10) #define PR_SERVICE_DELETE_FILES_W PROP_TAG( PT_MV_UNICODE, 0x3D10) #define PR_SERVICE_DELETE_FILES_A PROP_TAG( PT_MV_STRING8, 0x3D10) #define PR_AB_SEARCH_PATH_UPDATE PROP_TAG( PT_BINARY, 0x3D11) #define PR_PROFILE_NAME PROP_TAG( PT_TSTRING, 0x3D12) #define PR_PROFILE_NAME_A PROP_TAG( PT_STRING8, 0x3D12) #define PR_PROFILE_NAME_W PROP_TAG( PT_UNICODE, 0x3D12) #define PR_IDENTITY_DISPLAY PROP_TAG( PT_TSTRING, 0x3E00) #define PR_IDENTITY_DISPLAY_W PROP_TAG( PT_UNICODE, 0x3E00) #define PR_IDENTITY_DISPLAY_A PROP_TAG( PT_STRING8, 0x3E00) #define PR_IDENTITY_ENTRYID PROP_TAG( PT_BINARY, 0x3E01) #define PR_RESOURCE_METHODS PROP_TAG( PT_LONG, 0x3E02) #define PR_RESOURCE_TYPE PROP_TAG( PT_LONG, 0x3E03) #define PR_STATUS_CODE PROP_TAG( PT_LONG, 0x3E04) #define PR_IDENTITY_SEARCH_KEY PROP_TAG( PT_BINARY, 0x3E05) #define PR_OWN_STORE_ENTRYID PROP_TAG( PT_BINARY, 0x3E06) #define PR_RESOURCE_PATH PROP_TAG( PT_TSTRING, 0x3E07) #define PR_RESOURCE_PATH_W PROP_TAG( PT_UNICODE, 0x3E07) #define PR_RESOURCE_PATH_A PROP_TAG( PT_STRING8, 0x3E07) #define PR_STATUS_STRING PROP_TAG( PT_TSTRING, 0x3E08) #define PR_STATUS_STRING_W PROP_TAG( PT_UNICODE, 0x3E08) #define PR_STATUS_STRING_A PROP_TAG( PT_STRING8, 0x3E08) #define PR_X400_DEFERRED_DELIVERY_CANCEL PROP_TAG( PT_BOOLEAN, 0x3E09) #define PR_HEADER_FOLDER_ENTRYID PROP_TAG( PT_BINARY, 0x3E0A) #define PR_REMOTE_PROGRESS PROP_TAG( PT_LONG, 0x3E0B) #define PR_REMOTE_PROGRESS_TEXT PROP_TAG( PT_TSTRING, 0x3E0C) #define PR_REMOTE_PROGRESS_TEXT_W PROP_TAG( PT_UNICODE, 0x3E0C) #define PR_REMOTE_PROGRESS_TEXT_A PROP_TAG( PT_STRING8, 0x3E0C) #define PR_REMOTE_VALIDATE_OK PROP_TAG( PT_BOOLEAN, 0x3E0D) #define PR_CONTROL_FLAGS PROP_TAG( PT_LONG, 0x3F00) #define PR_CONTROL_STRUCTURE PROP_TAG( PT_BINARY, 0x3F01) #define PR_CONTROL_TYPE PROP_TAG( PT_LONG, 0x3F02) #define PR_DELTAX PROP_TAG( PT_LONG, 0x3F03) #define PR_DELTAY PROP_TAG( PT_LONG, 0x3F04) #define PR_XPOS PROP_TAG( PT_LONG, 0x3F05) #define PR_YPOS PROP_TAG( PT_LONG, 0x3F06) #define PR_CONTROL_ID PROP_TAG( PT_BINARY, 0x3F07) #define PR_INITIAL_DETAILS_PANE PROP_TAG( PT_LONG, 0x3F08) #define PR_MSG_EDITOR_FORMAT PROP_TAG( PT_LONG, 0x5903) #define PR_ATTACHMENT_HIDDEN PROP_TAG( PT_BOOLEAN, 0x7ffe) #define PR_SMTP_ADDRESS PROP_TAG( PT_TSTRING, 0x39fe) #define PR_SMTP_ADDRESS_W PROP_TAG( PT_UNICODE, 0x39fe) #define PR_SMTP_ADDRESS_A PROP_TAG( PT_STRING8, 0x39fe) #define PR_SENT_REPRESENTING_SMTP_ADDRESS PROP_TAG( PT_TSTRING, 0x5d02) #define PR_SENT_REPRESENTING_SMTP_ADDRESS_A PROP_TAG( PT_STRING8, 0x5d02) #define PR_SENT_REPRESENTING_SMTP_ADDRESS_W PROP_TAG( PT_UNICODE, 0x5d02) #define PidTagSenderSmtpAddress_W PROP_TAG( PT_UNICODE, 0x5d01) #define PR_BLOCK_STATUS PROP_TAG( PT_LONG, 0x1096) +#define PR_INTERNET_CPID PROP_TAG( PT_LONG, 0x3FDE) #define PROP_ID_SECURE_MIN 0x67F0 #define PROP_ID_SECURE_MAX 0x67FF #define pidExchangeXmitReservedMin 0x3FE0 #define pidExchangeNonXmitReservedMin 0x65E0 #define pidProfileMin 0x6600 #define pidStoreMin 0x6618 #define pidFolderMin 0x6638 #define pidMessageReadOnlyMin 0x6640 #define pidMessageWriteableMin 0x6658 #define pidAttachReadOnlyMin 0x666C #define pidSpecialMin 0x6670 #define pidAdminMin 0x6690 #define pidSecureProfileMin PROP_ID_SECURE_MIN #define PR_PROFILE_VERSION PROP_TAG( PT_LONG, pidProfileMin+0x00) #define PR_PROFILE_CONFIG_FLAGS PROP_TAG( PT_LONG, pidProfileMin+0x01) #define PR_PROFILE_HOME_SERVER PROP_TAG( PT_STRING8, pidProfileMin+0x02) #define PR_PROFILE_HOME_SERVER_DN PROP_TAG( PT_STRING8, pidProfileMin+0x12) #define PR_PROFILE_HOME_SERVER_ADDRS PROP_TAG( PT_MV_STRING8, \ pidProfileMin+0x13) #define PR_PROFILE_USER PROP_TAG( PT_STRING8, pidProfileMin+0x03) #define PR_PROFILE_CONNECT_FLAGS PROP_TAG( PT_LONG, pidProfileMin+0x04) #define PR_PROFILE_TRANSPORT_FLAGS PROP_TAG( PT_LONG, pidProfileMin+0x05) #define PR_PROFILE_UI_STATE PROP_TAG( PT_LONG, pidProfileMin+0x06) #define PR_PROFILE_UNRESOLVED_NAME PROP_TAG( PT_STRING8, pidProfileMin+0x07) #define PR_PROFILE_UNRESOLVED_SERVER PROP_TAG( PT_STRING8, pidProfileMin+0x08) #define PR_PROFILE_BINDING_ORDER PROP_TAG( PT_STRING8, pidProfileMin+0x09) #define PR_PROFILE_MAX_RESTRICT PROP_TAG( PT_LONG, pidProfileMin+0x0D) #define PR_PROFILE_AB_FILES_PATH PROP_TAG( PT_STRING8, pidProfileMin+0xE) #define PR_PROFILE_OFFLINE_STORE_PATH PROP_TAG( PT_STRING8,pidProfileMin+0x10) #define PR_PROFILE_OFFLINE_INFO PROP_TAG( PT_BINARY, pidProfileMin+0x11) #define PR_PROFILE_ADDR_INFO PROP_TAG( PT_BINARY, pidSpecialMin+0x17) #define PR_PROFILE_OPTIONS_DATA PROP_TAG( PT_BINARY, pidSpecialMin+0x19) #define PR_PROFILE_SECURE_MAILBOX PROP_TAG( PT_BINARY, \ pidSecureProfileMin + 0) #define PR_DISABLE_WINSOCK PROP_TAG( PT_LONG, pidProfileMin+0x18) #define PR_OST_ENCRYPTION PROP_TAG( PT_LONG, 0x6702) #define PR_PROFILE_OPEN_FLAGS PROP_TAG( PT_LONG, pidProfileMin+0x09) #define PR_PROFILE_TYPE PROP_TAG( PT_LONG, pidProfileMin+0x0A) #define PR_PROFILE_MAILBOX PROP_TAG( PT_STRING8, pidProfileMin+0x0B) #define PR_PROFILE_SERVER PROP_TAG( PT_STRING8, pidProfileMin+0x0C) #define PR_PROFILE_SERVER_DN PROP_TAG( PT_STRING8, pidProfileMin+0x14) #define PR_PROFILE_FAVFLD_DISPLAY_NAME PROP_TAG(PT_STRING8,pidProfileMin+0x0F) #define PR_PROFILE_FAVFLD_COMMENT PROP_TAG(PT_STRING8, pidProfileMin+0x15) #define PR_PROFILE_ALLPUB_DISPLAY_NAME PROP_TAG(PT_STRING8,pidProfileMin+0x16) #define PR_PROFILE_ALLPUB_COMMENT PROP_TAG(PT_STRING8, pidProfileMin+0x17) #define OSTF_NO_ENCRYPTION 0x80000000 #define OSTF_COMPRESSABLE_ENCRYPTION 0x40000000 #define OSTF_BEST_ENCRYPTION 0x20000000 #define PR_NON_IPM_SUBTREE_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x08) #define PR_EFORMS_REGISTRY_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x09) #define PR_SPLUS_FREE_BUSY_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x0A) #define PR_OFFLINE_ADDRBOOK_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x0B) #define PR_EFORMS_FOR_LOCALE_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x0C) #define PR_FREE_BUSY_FOR_LOCAL_SITE_ENTRYID \ PROP_TAG( PT_BINARY, pidStoreMin+0x0D) #define PR_ADDRBOOK_FOR_LOCAL_SITE_ENTRYID \ PROP_TAG( PT_BINARY, pidStoreMin+0x0E) #define PR_OFFLINE_MESSAGE_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x0F) #define PR_IPM_FAVORITES_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x18) #define PR_IPM_PUBLIC_FOLDERS_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x19) #define PR_GW_MTSIN_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x10) #define PR_GW_MTSOUT_ENTRYID PROP_TAG( PT_BINARY, pidStoreMin+0x11) #define PR_TRANSFER_ENABLED PROP_TAG( PT_BOOLEAN,pidStoreMin+0x12) #define PR_TEST_LINE_SPEED PROP_TAG( PT_BINARY, pidStoreMin+0x13) #define PR_HIERARCHY_SYNCHRONIZER PROP_TAG( PT_OBJECT, pidStoreMin+0x14) #define PR_CONTENTS_SYNCHRONIZER PROP_TAG( PT_OBJECT, pidStoreMin+0x15) #define PR_COLLECTOR PROP_TAG( PT_OBJECT, pidStoreMin+0x16) #define PR_FAST_TRANSFER PROP_TAG( PT_OBJECT, pidStoreMin+0x17) #define PR_STORE_OFFLINE PROP_TAG( PT_BOOLEAN,pidStoreMin+0x1A) #define PR_IN_TRANSIT PROP_TAG( PT_BOOLEAN,pidStoreMin) #define PR_REPLICATION_STYLE PROP_TAG( PT_LONG, pidAdminMin) #define PR_REPLICATION_SCHEDULE PROP_TAG( PT_BINARY, pidAdminMin+0x01) #define PR_REPLICATION_MESSAGE_PRIORITY PROP_TAG( PT_LONG, pidAdminMin+0x02) #define PR_OVERALL_MSG_AGE_LIMIT PROP_TAG( PT_LONG, pidAdminMin+0x03 ) #define PR_REPLICATION_ALWAYS_INTERVAL PROP_TAG( PT_LONG, pidAdminMin+0x04 ) #define PR_REPLICATION_MSG_SIZE PROP_TAG( PT_LONG, pidAdminMin+0x05 ) #define REPLICATION_MESSAGE_SIZE_LIMIT_DEFAULT 100 #define STYLE_DEFAULT (-1) #define STYLE_NEVER 0 #define STYLE_NORMAL 1 #define STYLE_ALWAYS 2 #define STYLE_ALWAYS_INTERVAL_DEFAULT 15 #define PR_SOURCE_KEY PROP_TAG( PT_BINARY, pidExchangeNonXmitReservedMin+0x0) #define PR_PARENT_SOURCE_KEY \ PROP_TAG( PT_BINARY, pidExchangeNonXmitReservedMin+0x1) #define PR_CHANGE_KEY PROP_TAG( PT_BINARY, pidExchangeNonXmitReservedMin+0x2) #define PR_PREDECESSOR_CHANGE_LIST \ PROP_TAG( PT_BINARY, pidExchangeNonXmitReservedMin+0x3) #define PR_FOLDER_CHILD_COUNT PROP_TAG( PT_LONG, pidFolderMin) #define PR_RIGHTS PROP_TAG( PT_LONG, pidFolderMin+1) #define PR_HAS_RULES PROP_TAG( PT_BOOLEAN, pidFolderMin+2) #define PR_ACL_TABLE PROP_TAG( PT_OBJECT, pidExchangeXmitReservedMin) #define PR_RULES_TABLE PROP_TAG( PT_OBJECT, pidExchangeXmitReservedMin+1) #define PR_ADDRESS_BOOK_ENTRYID PROP_TAG( PT_BINARY, pidFolderMin+0x03) #define PR_ACL_DATA PROP_TAG( PT_BINARY, pidExchangeXmitReservedMin) #define PR_RULES_DATA PROP_TAG( PT_BINARY, pidExchangeXmitReservedMin+0x1) #define PR_FOLDER_DESIGN_FLAGS \ PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0x2) #define PR_DESIGN_IN_PROGRESS \ PROP_TAG( PT_BOOLEAN, pidExchangeXmitReservedMin+0x4) #define PR_SECURE_ORIGINATION \ PROP_TAG( PT_BOOLEAN, pidExchangeXmitReservedMin+0x5) #define PR_PUBLISH_IN_ADDRESS_BOOK \ PROP_TAG( PT_BOOLEAN, pidExchangeXmitReservedMin+0x6) #define PR_RESOLVE_METHOD PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0x7) #define PR_ADDRESS_BOOK_DISPLAY_NAME \ PROP_TAG( PT_TSTRING, pidExchangeXmitReservedMin+0x8) #define PR_EFORMS_LOCALE_ID PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0x9) #define PR_REPLICA_LIST PROP_TAG( PT_BINARY, pidAdminMin+0x8) #define PR_OVERALL_AGE_LIMIT PROP_TAG( PT_LONG, pidAdminMin+0x9) #define RESOLVE_METHOD_DEFAULT 0 #define RESOLVE_METHOD_LAST_WRITER_WINS 1 #define RESOLVE_METHOD_NO_CONFLICT_NOTIFICATION 2 #define PR_PUBLIC_FOLDER_ENTRYID PROP_TAG( PT_BINARY, pidFolderMin+0x04) #define PR_HAS_NAMED_PROPERTIES \ PROP_TAG(PT_BOOLEAN, pidMessageReadOnlyMin+0x0A) #define PR_CREATOR_NAME \ PROP_TAG(PT_TSTRING, pidExchangeXmitReservedMin+0x18) #define PR_CREATOR_ENTRYID \ PROP_TAG(PT_BINARY, pidExchangeXmitReservedMin+0x19) #define PR_LAST_MODIFIER_NAME \ PROP_TAG(PT_TSTRING, pidExchangeXmitReservedMin+0x1A) #define PR_LAST_MODIFIER_ENTRYID \ PROP_TAG(PT_BINARY, pidExchangeXmitReservedMin+0x1B) #define PR_HAS_DAMS \ PROP_TAG( PT_BOOLEAN, pidExchangeXmitReservedMin+0xA) #define PR_RULE_TRIGGER_HISTORY \ PROP_TAG( PT_BINARY, pidExchangeXmitReservedMin+0x12) #define PR_MOVE_TO_STORE_ENTRYID \ PROP_TAG( PT_BINARY, pidExchangeXmitReservedMin+0x13) #define PR_MOVE_TO_FOLDER_ENTRYID \ PROP_TAG( PT_BINARY, pidExchangeXmitReservedMin+0x14) #define PR_REPLICA_SERVER \ PROP_TAG(PT_TSTRING, pidMessageReadOnlyMin+0x4) #define PR_DEFERRED_SEND_NUMBER \ PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0xB) #define PR_DEFERRED_SEND_UNITS \ PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0xC) #define PR_EXPIRY_NUMBER \ PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0xD) #define PR_EXPIRY_UNITS \ PROP_TAG( PT_LONG, pidExchangeXmitReservedMin+0xE) #define PR_DEFERRED_SEND_TIME \ PROP_TAG( PT_SYSTIME, pidExchangeXmitReservedMin+0xF) #define PR_GW_ADMIN_OPERATIONS PROP_TAG( PT_LONG, pidMessageWriteableMin) #define PR_P1_CONTENT PROP_TAG( PT_BINARY, 0x1100) #define PR_P1_CONTENT_TYPE PROP_TAG( PT_BINARY, 0x1101) #define PR_CLIENT_ACTIONS PROP_TAG(PT_BINARY, pidMessageReadOnlyMin+0x5) #define PR_DAM_ORIGINAL_ENTRYID PROP_TAG(PT_BINARY, pidMessageReadOnlyMin+0x6) #define PR_DAM_BACK_PATCHED PROP_TAG(PT_BOOLEAN, pidMessageReadOnlyMin+0x7) #define PR_RULE_ERROR PROP_TAG(PT_LONG, pidMessageReadOnlyMin+0x8) #define PR_RULE_ACTION_TYPE PROP_TAG(PT_LONG, pidMessageReadOnlyMin+0x9) #define PR_RULE_ACTION_NUMBER PROP_TAG(PT_LONG, pidMessageReadOnlyMin+0x10) #define PR_RULE_FOLDER_ENTRYID PROP_TAG(PT_BINARY, pidMessageReadOnlyMin+0x11) #define PR_CONFLICT_ENTRYID \ PROP_TAG(PT_BINARY, pidExchangeXmitReservedMin+0x10) #define PR_MESSAGE_LOCALE_ID \ PROP_TAG(PT_LONG, pidExchangeXmitReservedMin+0x11) #define PR_STORAGE_QUOTA_LIMIT \ PROP_TAG(PT_LONG, pidExchangeXmitReservedMin+0x15) #define PR_EXCESS_STORAGE_USED \ PROP_TAG(PT_LONG, pidExchangeXmitReservedMin+0x16) #define PR_SVR_GENERATING_QUOTA_MSG \ PROP_TAG(PT_TSTRING, pidExchangeXmitReservedMin+0x17) #define PR_DELEGATED_BY_RULE \ PROP_TAG( PT_BOOLEAN, pidExchangeXmitReservedMin+0x3) #define MSGSTATUS_IN_CONFLICT 0x800 #define PR_IN_CONFLICT PROP_TAG(PT_BOOLEAN, pidAttachReadOnlyMin) #define PR_LONGTERM_ENTRYID_FROM_TABLE PROP_TAG(PT_BINARY, pidSpecialMin) #define PR_ORIGINATOR_NAME PROP_TAG( PT_TSTRING, pidMessageWriteableMin+0x3) #define PR_ORIGINATOR_ADDR PROP_TAG( PT_TSTRING, pidMessageWriteableMin+0x4) #define PR_ORIGINATOR_ADDRTYPE \ PROP_TAG( PT_TSTRING, pidMessageWriteableMin+0x5) #define PR_ORIGINATOR_ENTRYID PROP_TAG( PT_BINARY, pidMessageWriteableMin+0x6) #define PR_ARRIVAL_TIME PROP_TAG( PT_SYSTIME, pidMessageWriteableMin+0x7) #define PR_TRACE_INFO PROP_TAG( PT_BINARY, pidMessageWriteableMin+0x8) #define PR_INTERNAL_TRACE_INFO \ PROP_TAG( PT_BINARY, pidMessageWriteableMin+0x12) #define PR_SUBJECT_TRACE_INFO PROP_TAG( PT_BINARY, pidMessageWriteableMin+0x9) #define PR_RECIPIENT_NUMBER PROP_TAG( PT_LONG, pidMessageWriteableMin+0xA) #define PR_MTS_SUBJECT_ID PROP_TAG(PT_BINARY, pidMessageWriteableMin+0xB) #define PR_REPORT_DESTINATION_NAME \ PROP_TAG(PT_TSTRING, pidMessageWriteableMin+0xC) #define PR_REPORT_DESTINATION_ENTRYID \ PROP_TAG(PT_BINARY, pidMessageWriteableMin+0xD) #define PR_CONTENT_SEARCH_KEY PROP_TAG(PT_BINARY, pidMessageWriteableMin+0xE) #define PR_FOREIGN_ID PROP_TAG(PT_BINARY, pidMessageWriteableMin+0xF) #define PR_FOREIGN_REPORT_ID PROP_TAG(PT_BINARY, pidMessageWriteableMin+0x10) #define PR_FOREIGN_SUBJECT_ID PROP_TAG(PT_BINARY, pidMessageWriteableMin+0x11) #define PR_MTS_ID PR_MESSAGE_SUBMISSION_ID #define PR_MTS_REPORT_ID PR_MESSAGE_SUBMISSION_ID #define PR_FOLDER_FLAGS PROP_TAG( PT_LONG, pidAdminMin+0x18 ) #define PR_LAST_ACCESS_TIME PROP_TAG( PT_SYSTIME,pidAdminMin+0x19) #define PR_RESTRICTION_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1A ) #define PR_CATEG_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1B ) #define PR_CACHED_COLUMN_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1C ) #define PR_NORMAL_MSG_W_ATTACH_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1D ) #define PR_ASSOC_MSG_W_ATTACH_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1E ) #define PR_RECIPIENT_ON_NORMAL_MSG_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x1F ) #define PR_RECIPIENT_ON_ASSOC_MSG_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x20 ) #define PR_ATTACH_ON_NORMAL_MSG_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x21 ) #define PR_ATTACH_ON_ASSOC_MSG_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x22 ) #define PR_NORMAL_MESSAGE_SIZE PROP_TAG( PT_LONG, pidAdminMin+0x23 ) #define PR_NORMAL_MESSAGE_SIZE_EXTENDED PROP_TAG( PT_I8, pidAdminMin+0x23 ) #define PR_ASSOC_MESSAGE_SIZE PROP_TAG( PT_LONG, pidAdminMin+0x24 ) #define PR_ASSOC_MESSAGE_SIZE_EXTENDED PROP_TAG( PT_I8, pidAdminMin+0x24 ) #define PR_FOLDER_PATHNAME PROP_TAG(PT_TSTRING, pidAdminMin+0x25 ) #define PR_OWNER_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x26 ) #define PR_CONTACT_COUNT PROP_TAG( PT_LONG, pidAdminMin+0x27 ) #define PR_MESSAGE_SIZE_EXTENDED PROP_TAG(PT_I8, PROP_ID(PR_MESSAGE_SIZE)) #endif /*MAPITAGS_H*/