diff --git a/src/Makefile.am b/src/Makefile.am index d605c20..026f031 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,135 +1,136 @@ # Makefile.am - makefile for GPGol # Copyright (C) 2005 g10 Code GmbH # Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik # Software engineering by Intevation GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ## Process this file with automake to produce Makefile.in unused_sources = item-events.cpp SUBDIRS = icons bin_PROGRAMS = gpgol EXTRA_DIST = \ versioninfo.rc.in mapi32.def $(unused_sources) Outlook.gpl \ dialogs.rc EXEEXT = .dll AM_CFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared AM_CXXFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared -std=c++11 AM_CXXFLAGS += $(GPGMEPP_CXXFLAGS) -D_FILE_OFFSET_BITS=64 gpgol_SOURCES = \ main.c gpgol.def \ resource.rc \ gpgol-ids.h \ olflange.cpp olflange.h \ olflange-def.h \ olflange-dlgs.cpp \ dialogs.h \ myexchext.h \ display.cpp display.h \ message.cpp message.h \ revert.cpp revert.h \ mimeparser.c mimeparser.h \ mimemaker.cpp mimemaker.h \ msgcache.c msgcache.h \ engine.c engine.h \ engine-assuan.c engine-assuan.h \ rfc822parse.c rfc822parse.h \ common.h common.c \ common_indep.h common_indep.c \ xmalloc.h \ config-dialog.c \ mapihelp.cpp mapihelp.h \ mymapi.h mymapitags.h \ serpent.c serpent.h \ ext-commands.cpp ext-commands.h \ user-events.cpp user-events.h \ session-events.cpp session-events.h \ message-events.cpp message-events.h \ attached-file-events.cpp attached-file-events.h \ property-sheets.cpp property-sheets.h \ item-events.h \ oomhelp.cpp oomhelp.h eventsink.h \ explorers.cpp explorers.h \ inspectors.cpp inspectors.h \ mailitem.cpp mailitem.h \ cmdbarcontrols.cpp cmdbarcontrols.h \ w32-gettext.c w32-gettext.h \ gpgoladdin.cpp gpgoladdin.h \ ribbon-callbacks.cpp ribbon-callbacks.h \ parsetlv.c parsetlv.h \ filetype.c filetype.h \ eventsinks.h application-events.cpp \ mailitem-events.cpp \ attachment.h attachment.cpp \ windowmessages.h windowmessages.cpp \ gpgolstr.h gpgolstr.cpp \ mail.h mail.cpp \ rfc2047parse.h rfc2047parse.c \ mlang-charset.cpp mlang-charset.h \ gmime-table-private.h \ exechelp.c exechelp.h \ addin-options.cpp addin-options.h \ parsecontroller.cpp parsecontroller.h \ mimedataprovider.cpp mimedataprovider.h \ explorer-events.cpp explorers-events.cpp \ cryptcontroller.cpp cryptcontroller.h \ - cpphelp.cpp cpphelp.h + cpphelp.cpp cpphelp.h \ + wks-helper.cpp wks-helper.h #treeview_SOURCES = treeview.c # W32API 3.2 comes with an unusable libmapi32.a. We build our own # version. Note the omission of -k (--kill-at) from the DLLTOOL # command line. We also create our own virtual copies to the _static_ # versions of GPGME and gpg-error, because we want to link to them # statically, and not dynamically (otherwise Outlook would not find # them). gpgol_DEPENDENCIES = libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a if BUILD_W64 DLLTOOLFLAGS64=--as-flags=--64 -m i386:x86-64 endif libmapi32.a: mapi32.def $(DLLTOOL) $(DLLTOOLFLAGS64) --output-lib $@ --def $< libgpg-error.a: ln -s $$($(GPG_ERROR_CONFIG) --prefix)/lib/libgpg-error.a . libgpgme.a: ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgme.a . libassuan.a: ln -s $$($(LIBASSUAN_CONFIG) --prefix)/lib/libassuan.a . libgpgmepp.a: ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgmepp.a . clean-local: rm -f libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a gpgol_LDFLAGS = -static-libgcc -static-libstdc++ gpgol_LDADD = $(srcdir)/gpgol.def \ -L . -lgpgmepp -lgpgme -lassuan -lgpg-error \ -lmapi32 -lshell32 -lgdi32 -lcomdlg32 \ -lole32 -loleaut32 -lws2_32 -ladvapi32 \ -luuid -lgdiplus -lrpcrt4 resource.o: resource.rc versioninfo.rc dialogs.rc dialogs.h .rc.o: $(WINDRES) -I $(srcdir) -I . -I .. `test -f '$<' || echo '$(srcdir)/'`$< $@ diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp index eee123d..c45102a 100644 --- a/src/cryptcontroller.cpp +++ b/src/cryptcontroller.cpp @@ -1,987 +1,991 @@ /* @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 #include #include #ifdef HAVE_W32_SYSTEM #include "common.h" /* We use UTF-8 internally. */ #undef _ # define _(a) utf8_gettext (a) #else # define _(a) a #endif #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, bool doInline, GpgME::Protocol proto): m_mail (mail), m_encrypt (encrypt), m_sign (sign), m_inline (doInline), 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, doInline); } CryptController::~CryptController() { stop_crypto_overlay(); log_debug ("%s:%s:%p", SRCNAME, __func__, m_mail); } 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 = NULL; } 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) { log_debug ("%s:%s: encrypt empty message", SRCNAME, __func__); } if (n_att_usable && m_inline) { log_debug ("%s:%s: PGP Inline not supported for attachments." " Using PGP MIME", SRCNAME, __func__); m_inline = false; } else if (m_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", SRCNAME, __func__); return -1; } if (m_encrypt && !recpFprs.size()) { log_error ("%s:%s: Encrypt requested but no recipient fingerprints", SRCNAME, __func__); return -1; } return lookup_fingerprints (sigFpr, recpFprs); } int CryptController::resolve_keys () { m_recipients.clear(); 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) 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 (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 char **recipients = m_mail->take_cached_recipients (); for (size_t i = 0; recipients && recipients[i]; i++) { args.push_back (GpgME::UserID::addrSpecFromString (recipients[i])); } release_cArray (recipients); } // 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); // Args are prepared. Spawn the resolver. auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen release_cArray (cargs); TRACEPOINT; return -1; } GpgME::Data mystdin (GpgME::Data::null), mystdout, mystderr; #ifdef DEBUG_RESOLVER log_debug ("Spawning args:"); for (size_t i = 0; cargs && cargs[i]; i++) { log_debug ("%i: '%s'", i, cargs[i]); } #endif 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()); } if (parse_output (mystdout)) { 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 0; } int CryptController::do_crypto () { log_debug ("%s:%s", SRCNAME, __func__); + /* Start a WKS check if necessary. */ + WKSHelper::instance()->start_check (m_mail->get_cached_sender ()); + if (resolve_keys ()) { log_debug ("%s:%s: Failure to resolve keys.", SRCNAME, __func__); return -2; } if (m_proto == GpgME::CMS && m_inline) { log_debug ("%s:%s: Inline for S/MIME not supported. Switching to mime.", SRCNAME, __func__); m_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__); 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 && m_inline) { // Sign encrypt combined const auto result_pair = ctx->signAndEncrypt (m_recipients, m_inline ? m_bodyInput : m_input, m_output, GpgME::Context::AlwaysTrust); if (result_pair.first.error() || result_pair.second.error()) { log_error ("%s:%s: Encrypt / Sign error %s %s.", SRCNAME, __func__, result_pair.first.error().asString(), result_pair.second.error().asString()); return -1; } if (result_pair.first.error().isCanceled() || result_pair.second.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_encrypt && m_sign) { // First sign then encrypt const auto sigResult = ctx->sign (m_input, m_output, GpgME::Detached); if (sigResult.error()) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, sigResult.error().asString()); return -1; } if (sigResult.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } parse_micalg (sigResult); // We now have plaintext in m_input // The detached signature in m_output // Set up the sink object to construct the multipart/signed GpgME::Data multipart; struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &multipart; sink->writefnc = sink_data_write; if (create_sign_attach (sink, m_proto == GpgME::CMS ? PROTOCOL_SMIME : PROTOCOL_OPENPGP, m_output, m_input, m_micalg.c_str ())) { TRACEPOINT; return -1; } // Now we have the multipart throw away the rest. m_output = GpgME::Data (); m_input = GpgME::Data (); multipart.seek (0, SEEK_SET); const auto encResult = ctx->encrypt (m_recipients, multipart, m_output, GpgME::Context::AlwaysTrust); if (encResult.error()) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, encResult.error().asString()); return -1; } if (encResult.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } // Now we have encrypted output just treat it like encrypted. } else if (m_encrypt) { const auto result = ctx->encrypt (m_recipients, m_inline ? m_bodyInput : m_input, m_output, GpgME::Context::AlwaysTrust); if (result.error()) { log_error ("%s:%s: Encryption error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } } else if (m_sign) { const auto result = ctx->sign (m_inline ? m_bodyInput : m_input, m_output, m_inline ? GpgME::Clearsigned : GpgME::Detached); if (result.error()) { log_error ("%s:%s: Signing error %s.", SRCNAME, __func__, result.error().asString()); return -1; } if (result.error().isCanceled()) { log_debug ("%s:%s: User cancled", SRCNAME, __func__); return -2; } parse_micalg (result); } else { // ??? log_error ("%s:%s: unreachable code reached.", SRCNAME, __func__); } log_debug ("%s:%s: Crypto done sucessfuly.", SRCNAME, __func__); m_crypto_success = true; return 0; } static int write_data (sink_t sink, GpgME::Data &data) { if (!sink || !sink->writefnc) { return -1; } char buf[4096]; size_t nread; data.seek (0, SEEK_SET); while ((nread = data.read (buf, 4096)) > 0) { sink->writefnc (sink, buf, nread); } return 0; } int create_sign_attach (sink_t sink, protocol_t protocol, GpgME::Data &signature, GpgME::Data &signedData, const char *micalg) { char boundary[BOUNDARYSIZE+1]; char top_header[BOUNDARYSIZE+200]; int rc = 0; /* Write the top header. */ generate_boundary (boundary); create_top_signing_header (top_header, sizeof top_header, protocol, 1, boundary, micalg); if ((rc = write_string (sink, top_header))) { TRACEPOINT; return rc; } /* Write the boundary so that it is not included in the hashing. */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } /* Write the signed mime structure */ if ((rc = write_data (sink, signedData))) { TRACEPOINT; return rc; } /* Write the signature attachment */ if ((rc = write_boundary (sink, boundary, 0))) { TRACEPOINT; return rc; } if (protocol == PROTOCOL_OPENPGP) { rc = write_string (sink, "Content-Type: application/pgp-signature\r\n"); } else { rc = write_string (sink, "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-signature\r\n"); /* rc = write_string (sink, */ /* "Content-Type: application/x-pkcs7-signature\r\n" */ /* "\tname=\"smime.p7s\"\r\n" */ /* "Content-Transfer-Encoding: base64\r\n" */ /* "Content-Disposition: attachment;\r\n" */ /* "\tfilename=\"smime.p7s\"\r\n"); */ } if (rc) { TRACEPOINT; return rc; } if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return rc; } // Write the signature data if (protocol == PROTOCOL_SMIME) { const std::string sigStr = signature.toString(); if ((rc = write_b64 (sink, (const void *) sigStr.c_str (), sigStr.size()))) { TRACEPOINT; return rc; } } else if ((rc = write_data (sink, signature))) { TRACEPOINT; return rc; } // Add an extra linefeed with should not harm. if ((rc = write_string (sink, "\r\n"))) { TRACEPOINT; return rc; } /* Write the final boundary. */ if ((rc = write_boundary (sink, boundary, 1))) { TRACEPOINT; return rc; } return rc; } static int create_encrypt_attach (sink_t sink, protocol_t protocol, GpgME::Data &encryptedData) { char boundary[BOUNDARYSIZE+1]; int rc = create_top_encryption_header (sink, protocol, boundary, false); // 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) { 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_inline) { // Nothing to do for inline. log_debug ("%s:%s: Inline mail. No MAPI update.", SRCNAME, __func__); return 0; } LPMESSAGE message = get_oom_base_message (m_mail->item()); if (!message) { log_error ("%s:%s: Failed to obtain message.", SRCNAME, __func__); return -1; } mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0); // Set up the sink object for our MSOXSMIME attachment. struct sink_s sinkmem; sink_t sink = &sinkmem; memset (sink, 0, sizeof *sink); sink->cb_data = &m_input; sink->writefnc = sink_data_write; LPATTACH attach = create_mapi_attachment (message, sink); 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; if (m_sign && m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output); } else if (m_encrypt) { rc = create_encrypt_attach (sink, protocol, m_output); } 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_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::stop_crypto_overlay () { if (m_overlayCtx) { log_debug ("%s:%s: Stopping crypto overlay.", SRCNAME, __func__); m_overlayStdin.write ("quit\n", 5); m_overlayCtx = nullptr; } } void CryptController::start_crypto_overlay () { std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; return; } const auto overlayer = std::string (gpg4win_dir) + "\\bin\\overlayer.exe"; xfree (gpg4win_dir); args.push_back (overlayer); 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) wnd)); } args.push_back (std::string ("--overlayText")); if (m_encrypt) { args.push_back (std::string (_("Encrypting..."))); } else if (m_sign) { args.push_back (std::string (_("Signing..."))); } char **cargs = vector_to_cArray (args); m_overlayCtx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!m_overlayCtx) { // can't happen release_cArray (cargs); TRACEPOINT; return; } GpgME::Data mystderr(GpgME::Data::null); GpgME::Data mystdout(GpgME::Data::null); GpgME::Error err = m_overlayCtx->spawnAsync (cargs[0], const_cast (cargs), m_overlayStdin, mystdout, mystderr, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); #ifdef DEBUG_RESOLVER log_debug ("Overlayer args:"); for (size_t i = 0; cargs && cargs[i]; i++) { log_debug ("%i: '%s'", i, cargs[i]); } #endif release_cArray (cargs); } diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp new file mode 100644 index 0000000..efe3bce --- /dev/null +++ b/src/wks-helper.cpp @@ -0,0 +1,236 @@ +/* wks-helper.cpp - Web Key Services for GpgOL + * 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 "wks-helper.h" + +#include "common.h" +#include "cpphelp.h" + +#include + +#include + +#include +#include +#include +#include + +#define CHECK_MIN_INTERVAL (60 * 60 * 24 * 7) + +static std::map s_states; +static std::map s_last_checked; + +static WKSHelper* singleton = NULL; + +GPGRT_LOCK_DEFINE (wks_lock); + +WKSHelper::WKSHelper() +{ + load (); +} + +WKSHelper::~WKSHelper () +{ + // Ensure that we are not destroyed while + // worker is running. + gpgrt_lock_lock (&wks_lock); + gpgrt_lock_unlock (&wks_lock); +} + +const WKSHelper* +WKSHelper::instance () +{ + if (!singleton) + { + singleton = new WKSHelper (); + } + return singleton; +} + +WKSHelper::WKSState +WKSHelper::get_state (const std::string &mbox) const +{ + gpgrt_lock_lock (&wks_lock); + const auto it = s_states.find(mbox); + const auto dataEnd = s_states.end(); + gpgrt_lock_unlock (&wks_lock); + if (it == dataEnd) + { + return NotChecked; + } + return it->second; +} + +time_t +WKSHelper::get_check_time (const std::string &mbox) const +{ + gpgrt_lock_lock (&wks_lock); + const auto it = s_last_checked.find(mbox); + const auto dataEnd = s_last_checked.end(); + gpgrt_lock_unlock (&wks_lock); + if (it == dataEnd) + { + return 0; + } + return it->second; +} + +static std::string +get_wks_client_path () +{ + char *gpg4win_dir = get_gpg4win_dir (); + if (!gpg4win_dir) + { + TRACEPOINT; + return std::string (); + } + const auto ret = std::string (gpg4win_dir) + + "\\..\\GnuPG\\bin\\gpg-wks-client.exe"; + xfree (gpg4win_dir); + + if (!access (ret.c_str (), F_OK)) + { + return ret; + } + log_debug ("%s:%s: Failed to find wks-client in '%s'", + SRCNAME, __func__, ret.c_str ()); + return std::string (); +} + +static DWORD WINAPI +do_check (LPVOID arg) +{ + const auto wksPath = get_wks_client_path (); + + if (wksPath.empty()) + { + return 0; + } + + std::vector args; + const auto mbox = std::string ((char *) arg); + xfree (arg); + + args.push_back (wksPath); + args.push_back (std::string ("--status-fd")); + args.push_back (std::string ("1")); + args.push_back (std::string ("--supported")); + args.push_back (mbox); + + // Spawn the process + auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); + + if (!ctx) + { + TRACEPOINT; + return 0; + } + + GpgME::Data mystdin, mystdout, mystderr; + + char **cargs = vector_to_cArray (args); + + GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), + mystdin, mystdout, mystderr, + GpgME::Context::SpawnNone); + release_cArray (cargs); + + if (err) + { + log_debug ("%s:%s: WKS client spawn code: %i asString: %s", + SRCNAME, __func__, err.code(), err.asString()); + return 0; + } + + auto data = mystdout.toString (); + rtrim (data); + + bool success = data == "[GNUPG:] SUCCESS"; + const auto state = success ? WKSHelper::Supported : WKSHelper::NotSupported; + + gpgrt_lock_lock (&wks_lock); + + auto it = s_states.find(mbox); + + // TODO figure out if it was published. + if (success) + { + log_debug ("%s:%s: WKS client: '%s' is supported", + SRCNAME, __func__, mbox.c_str ()); + } + if (it != s_states.end()) + { + it->second = state; + } + else + { + s_states.insert (std::make_pair (mbox, state)); + } + + auto tit = s_last_checked.find(mbox); + auto now = time (0); + if (tit != s_last_checked.end()) + { + tit->second = now; + } + else + { + s_last_checked.insert (std::make_pair (mbox, now)); + } + + gpgrt_lock_unlock (&wks_lock); + return 0; +} + +void +WKSHelper::start_check (const std::string &mbox, bool forced) const +{ + auto lastTime = get_check_time (mbox); + auto now = time (0); + if (!forced && lastTime && difftime (lastTime, now) < CHECK_MIN_INTERVAL) + { + /* Data is new enough */ + return; + } + + if (mbox.empty()) + { + log_debug ("%s:%s: start check called without mbox", + SRCNAME, __func__); + } + + log_debug ("%s:%s: WKSHelper starting check", + SRCNAME, __func__); + /* Start the actual work that can be done in a background thread. */ + CloseHandle (CreateThread (NULL, 0, do_check, strdup (mbox.c_str ()), 0, + NULL)); + return; +} + +void +WKSHelper::load () const +{ + // TODO +} + +void +WKSHelper::save () const +{ + // TODO +} diff --git a/src/wks-helper.h b/src/wks-helper.h new file mode 100644 index 0000000..29af516 --- /dev/null +++ b/src/wks-helper.h @@ -0,0 +1,80 @@ +/* @file wks-helper.cpp + * @brief Helper to work with a web-key-service + * + * 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 + +/** @brief Helper for web key services. + * + * Everything is public to make it easy to access data + * members from another windows thread. Don't mess with them. + */ +class WKSHelper +{ +protected: + /** Loads the list of checked keys */ + explicit WKSHelper (); +public: + enum WKSState + { + NotChecked, /*<-- Supported state was not checked */ + NotSupported, /* <-- WKS is not supported for this address */ + Supported, /* <-- WKS is supported for this address */ + NeedsPublish, /* <-- There was no key published for this address */ + NeedsUpdate, /* <-- Not yet implemeted. */ + RequestSent, /* <-- A publishing request has been sent. */ + }; + + ~WKSHelper (); + + /** Get the WKSHelper + + On the initial request: + Ensure that the OOM is available. + Will load all account addresses from OOM and then return. + + Starts a background thread to load info from a file + and run checks if necessary. + + When the thread is finished initialized will be true. + */ + static const WKSHelper* instance (); + + /** If the key for the address @address should be published */ + WKSState get_state (const std::string &mbox) const; + + /** Start a supported check for a given mbox. + + If force is true the check will be run. Otherwise + the state will only be updated if the last check + was more then 7 days ago. + + Returns immediately as the check is run in a background + thread. + */ + void start_check (const std::string &mbox, bool force = false) const; + +private: + time_t get_check_time (const std::string &mbox) const; + + void save() const; + void load() const;; +};