diff --git a/src/pgpmime.c b/src/pgpmime.c deleted file mode 100644 index f0dedfd..0000000 --- a/src/pgpmime.c +++ /dev/null @@ -1,721 +0,0 @@ -/* pgpmime.c - Try to handle PGP/MIME for Outlook - * Copyright (C) 2005 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 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#error not used - -/* - EXPLAIN what we are doing here. -*/ - - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#define COBJMACROS -#include -#include /* For IStream. */ - -#include - -#include "mymapi.h" -#include "mymapitags.h" -#include "rfc822parse.h" - -#include "common.h" -#include "pgpmime.h" -#include "engine.h" - - -/* The maximum length of a line we ar able to porcess. RFC822 alows - only for 1000 bytes; thus 2000 seems to be a reasonable value. */ -#define LINEBUFSIZE 2000 - - -/* The context object we use to track information. */ -struct pgpmime_context -{ - HWND hwnd; /* A window handle to be used for message boxes etc. */ - rfc822parse_t msg; /* The handle of the RFC822 parser. */ - - int preview; /* Do only decryption and pop up no message bozes. */ - - int verify_mode; /* True if we want to verify a PGP/MIME signature. */ - - int nesting_level; /* Current MIME nesting level. */ - int in_data; /* We are currently in data (body or attachment). */ - - gpgme_data_t signed_data;/* NULL or the data object used to collect - the signed data. It would bet better to - just hash it but there is no support in - gpgme for this yet. */ - gpgme_data_t sig_data; /* NULL or data object to collect the - signature attachment which should be a - signature then. */ - - gpgme_data_t body; /* NULL or a data object used to collect the - body part we are going to display later. */ - int collect_body; /* True if we are collecting the body lines. */ - int collect_attachment; /* True if we are collecting an attachment. */ - int collect_signeddata; /* True if we are collecting the signed data. */ - int collect_signature; /* True if we are collecting a signature. */ - int start_hashing; /* Flag used to start collecting signed data. */ - int hashing_level; /* MIME level where we started hashing. */ - int is_qp_encoded; /* Current part is QP encoded. */ - int is_base64_encoded; /* Current part is base 64 encoded. */ - int is_utf8; /* Current part has charset utf-8. */ - - int part_counter; /* Counts the number of processed parts. */ - char *filename; /* Current filename (malloced) or NULL. */ - - LPSTREAM outstream; /* NULL or a stream to write a part to. */ - - b64_state_t base64; /* The state of the BAse-64 decoder. */ - - int line_too_long; /* Indicates that a received line was too long. */ - int parser_error; /* Indicates that we encountered a error from - the parser. */ - - /* Buffer used to constructed complete files. */ - size_t linebufsize; /* The allocated size of the buffer. */ - size_t linebufpos; /* The actual write posituion. */ - char linebuf[1]; /* The buffer. */ -}; -typedef struct pgpmime_context *pgpmime_context_t; - - -/* This function is a wrapper around gpgme_data_write to convert the - data to utf-8 first. We assume Latin-1 here. */ -static int -latin1_data_write (gpgme_data_t data, const char *line, size_t len) -{ - const char *s; - char *buffer, *p; - size_t i, n; - int rc; - - for (s=line, i=0, n=0 ; i < len; s++, i++ ) - { - n++; - if (*s & 0x80) - n++; - } - buffer = xmalloc (n + 1); - for (s=line, i=0, p=buffer; i < len; s++, i++ ) - { - if (*s & 0x80) - { - *p++ = 0xc0 | ((*s >> 6) & 3); - *p++ = 0x80 | (*s & 0x3f); - } - else - *p++ = *s; - } - assert (p-buffer == n); - rc = gpgme_data_write (data, buffer, n); - xfree (buffer); - return rc; -} - - -/* Print the message event EVENT. */ -static void -debug_message_event (pgpmime_context_t ctx, rfc822parse_event_t event) -{ - const char *s; - - switch (event) - { - case RFC822PARSE_OPEN: s= "Open"; break; - case RFC822PARSE_CLOSE: s= "Close"; break; - case RFC822PARSE_CANCEL: s= "Cancel"; break; - case RFC822PARSE_T2BODY: s= "T2Body"; break; - case RFC822PARSE_FINISH: s= "Finish"; break; - case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; - case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; - case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; - case RFC822PARSE_BOUNDARY: s= "Boundary"; break; - case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; - case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; - case RFC822PARSE_PREAMBLE: s= "Preamble"; break; - case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; - default: s= "[unknown event]"; break; - } - log_debug ("%s: ctx=%p, rfc822 event %s\n", SRCNAME, ctx, s); -} - - - - -/* This routine gets called by the RFC822 parser for all kind of - events. OPAQUE carries in our case a pgpmime context. Should - return 0 on success or -1 and as well as seeting errno on - failure. */ -static int -message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) -{ - pgpmime_context_t ctx = opaque; - HRESULT hr; - - debug_message_event (ctx, event); - - if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY) - { - /* We need to check here whether to start collecting signed data - because attachments might come without header lines and thus - we won't see the BEGIN_HEADER event. */ - if (ctx->start_hashing == 1) - { - ctx->start_hashing = 2; - ctx->hashing_level = ctx->nesting_level; - ctx->collect_signeddata = 1; - gpgme_data_new (&ctx->signed_data); - } - } - - - if (event == RFC822PARSE_T2BODY) - { - rfc822parse_field_t field; - const char *s1, *s2, *s3; - size_t off; - char *p; - int is_text = 0; - - ctx->is_utf8 = 0; - field = rfc822parse_parse_field (msg, "Content-Type", -1); - if (field) - { - s1 = rfc822parse_query_media_type (field, &s2); - if (s1) - { - log_debug ("%s: ctx=%p, media `%s' `%s'\n", - SRCNAME, ctx, s1, s2); - - if (!strcmp (s1, "multipart")) - { - /* We don't care about the top level multipart layer - but wait until it comes to the actual parts which - then will get stored as attachments. - - For now encapsulated signed or encrypted - containers are not processed in a special way as - they should. Except for the simple verify - mode. */ - if (ctx->verify_mode && !ctx->signed_data - && !strcmp (s2,"signed") - && (s3 = rfc822parse_query_parameter (field, - "protocol", 0)) - && !strcmp (s3, "application/pgp-signature")) - { - /* Need to start the hashing after the next - boundary. */ - ctx->start_hashing = 1; - } - } - else if (!strcmp (s1, "text")) - { - is_text = 1; - } - else if (ctx->verify_mode - && ctx->nesting_level == 1 - && !ctx->sig_data - && !strcmp (s1, "application") - && !strcmp (s2, "pgp-signature")) - { - /* This is the second part of a PGP/MIME signature. - We only support here full messages thus checking - the nesting level is sufficient. We do this only - for the first signature (i.e. if sig_data has not - been set yet). We do this only while in verify - mode because we don't want to write a full MUA - (although this would be easier than to tame this - Outlook beast). */ - if (!ctx->preview && !gpgme_data_new (&ctx->sig_data)) - { - ctx->collect_signature = 1; - } - } - else /* Other type. */ - { - if (!ctx->preview) - ctx->collect_attachment = 1; - } - - } - - s1 = rfc822parse_query_parameter (field, "charset", 0); - if (s1 && !strcmp (s1, "utf-8")) - ctx->is_utf8 = 1; - - rfc822parse_release_field (field); - } - else - { - /* No content-type at all indicates text/plain. */ - is_text = 1; - } - ctx->in_data = 1; - - log_debug ("%s: this body: nesting=%d part_counter=%d is_text=%d\n", - SRCNAME, ctx->nesting_level, ctx->part_counter, is_text); - - /* Need to figure out the encoding. */ - ctx->is_qp_encoded = 0; - ctx->is_base64_encoded = 0; - p = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1, &off); - if (p) - { - if (!stricmp (p+off, "quoted-printable")) - ctx->is_qp_encoded = 1; - else if (!stricmp (p+off, "base64")) - { - ctx->is_base64_encoded = 1; - b64_init (&ctx->base64); - } - xfree (p); - } - - /* If this is a text part, decide whether we treat it as our body. */ - if (is_text) - { - /* If this is the first text part at all we will - start to collect it and use it later as the - regular body. An initialized ctx->BODY is an - indication that this is not the first text part - - we treat such a part like any other - attachment. */ - if (!ctx->body) - { - if (!gpgme_data_new (&ctx->body)) - ctx->collect_body = 1; - } - else if (!ctx->preview) - ctx->collect_attachment = 1; - } - - - if (ctx->collect_attachment) - { - /* Now that if we have an attachment prepare for writing it - out. */ - p = NULL; - field = rfc822parse_parse_field (msg, "Content-Disposition", -1); - if (field) - { - s1 = rfc822parse_query_parameter (field, "filename", 0); - if (s1) - p = xstrdup (s1); - rfc822parse_release_field (field); - } - if (!p) - { - p = xmalloc (50); - snprintf (p, 49, "unnamed-%d.dat", ctx->part_counter); - } - if (ctx->outstream) - { - IStream_Release (ctx->outstream); - ctx->outstream = NULL; - } - tryagain: - xfree (ctx->filename); - ctx->filename = ctx->preview? NULL:get_save_filename (ctx->hwnd, p); - if (!ctx->filename) - ctx->collect_attachment = 0; /* User does not want to save it. */ - else - { - hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer, - (STGM_CREATE | STGM_READWRITE), - ctx->filename, NULL, &ctx->outstream); - if (FAILED (hr)) - { - log_error ("%s:%s: can't create file `%s': hr=%#lx\n", - SRCNAME, __func__, ctx->filename, hr); - MessageBox (ctx->hwnd, _("Error creating file\n" - "Please select another one"), - _("I/O-Error"), MB_ICONERROR|MB_OK); - goto tryagain; - } - log_debug ("%s:%s: writing attachment to `%s'\n", - SRCNAME, __func__, ctx->filename); - } - xfree (p); - } - } - else if (event == RFC822PARSE_LEVEL_DOWN) - { - ctx->nesting_level++; - } - else if (event == RFC822PARSE_LEVEL_UP) - { - if (ctx->nesting_level) - ctx->nesting_level--; - else - { - log_error ("%s: ctx=%p, invalid structure: bad nesting level\n", - SRCNAME, ctx); - ctx->parser_error = 1; - } - } - else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY) - { - ctx->in_data = 0; - ctx->collect_body = 0; - ctx->collect_attachment = 0; - xfree (ctx->filename); - ctx->filename = NULL; - if (ctx->outstream) - { - IStream_Commit (ctx->outstream, 0); - IStream_Release (ctx->outstream); - ctx->outstream = NULL; - } - if (ctx->start_hashing == 2 && ctx->hashing_level == ctx->nesting_level) - { - ctx->start_hashing = 3; /* Avoid triggering it again. */ - ctx->collect_signeddata = 0; - } - } - else if (event == RFC822PARSE_BEGIN_HEADER) - { - ctx->part_counter++; - } - - return 0; -} - - -/* This handler is called by GPGME with the decrypted plaintext. */ -static ssize_t -plaintext_handler (void *handle, const void *buffer, size_t size) -{ - pgpmime_context_t ctx = handle; - const char *s; - size_t nleft, pos, len; - - s = buffer; - pos = ctx->linebufpos; - nleft = size; - for (; nleft ; nleft--, s++) - { - if (pos >= ctx->linebufsize) - { - log_error ("%s: ctx=%p, rfc822 parser failed: line too long\n", - SRCNAME, ctx); - ctx->line_too_long = 1; - return 0; /* Error. */ - } - if (*s != '\n') - ctx->linebuf[pos++] = *s; - else - { /* Got a complete line. Remove the last CR */ - if (pos && ctx->linebuf[pos-1] == '\r') - pos--; - - if (rfc822parse_insert (ctx->msg, ctx->linebuf, pos)) - { - log_error ("%s: ctx=%p, rfc822 parser failed: %s\n", - SRCNAME, ctx, strerror (errno)); - ctx->parser_error = 1; - return 0; /* Error. */ - } - - - if (ctx->collect_signeddata && ctx->signed_data) - { - /* Save the signed data. Note that we need to delay - the CR/LF because the last line ending belongs to the - next boundary. */ - if (ctx->collect_signeddata == 2) - gpgme_data_write (ctx->signed_data, "\r\n", 2); - gpgme_data_write (ctx->signed_data, ctx->linebuf, pos); - ctx->collect_signeddata = 2; - } - - if (ctx->in_data && ctx->collect_body && ctx->body) - { - /* We are inside the body of the message. Save it away - to a gpgme data object. Note that this is only - used for the first text part. */ - if (ctx->collect_body == 1) /* Need to skip the first line. */ - ctx->collect_body = 2; - else - { - if (ctx->is_qp_encoded) - len = qp_decode (ctx->linebuf, pos); - else if (ctx->is_base64_encoded) - len = b64_decode (&ctx->base64, ctx->linebuf, pos); - else - len = pos; - if (len) - { - if (ctx->is_utf8) - gpgme_data_write (ctx->body, ctx->linebuf, len); - else - latin1_data_write (ctx->body, ctx->linebuf, len); - } - if (!ctx->is_base64_encoded) - gpgme_data_write (ctx->body, "\r\n", 2); - } - } - else if (ctx->in_data && ctx->collect_attachment) - { - /* We are inside of an attachment part. Write it out. */ - if (ctx->collect_attachment == 1) /* Skip the first line. */ - ctx->collect_attachment = 2; - else if (ctx->outstream) - { - HRESULT hr = 0; - - if (ctx->is_qp_encoded) - len = qp_decode (ctx->linebuf, pos); - else if (ctx->is_base64_encoded) - len = b64_decode (&ctx->base64, ctx->linebuf, pos); - else - len = pos; - if (len) - hr = IStream_Write (ctx->outstream, ctx->linebuf, - len, NULL); - if (!hr && !ctx->is_base64_encoded) - hr = IStream_Write (ctx->outstream, "\r\n", 2, NULL); - if (hr) - { - log_debug ("%s:%s: Write failed: hr=%#lx", - SRCNAME, __func__, hr); - if (!ctx->preview) - MessageBox (ctx->hwnd, _("Error writing file"), - _("I/O-Error"), MB_ICONERROR|MB_OK); - ctx->parser_error = 1; - return 0; /* Error. */ - } - } - } - else if (ctx->in_data && ctx->collect_signature) - { - /* We are inside of a signature attachment part. */ - if (ctx->collect_signature == 1) /* Skip the first line. */ - ctx->collect_signature = 2; - else if (ctx->sig_data) - { - if (ctx->is_qp_encoded) - len = qp_decode (ctx->linebuf, pos); - else if (ctx->is_base64_encoded) - len = b64_decode (&ctx->base64, ctx->linebuf, pos); - else - len = pos; - if (len) - gpgme_data_write (ctx->sig_data, ctx->linebuf, len); - if (!ctx->is_base64_encoded) - gpgme_data_write (ctx->sig_data, "\r\n", 2); - } - } - - /* Continue with next line. */ - pos = 0; - } - } - ctx->linebufpos = pos; - - return size; -} - - -/* Decrypt the PGP/MIME INSTREAM (i.e the second part of the - multipart/mixed) and allow saving of all attachments. On success a - newly allocated body will be stored at BODY. If ATTESTATION is not - NULL a text with the result of the signature verification will get - printed to it. HWND is the window to be used for message box and - such. In PREVIEW_MODE no verification will be done, no messages - saved and no messages boxes will pop up. */ -int -pgpmime_decrypt (LPSTREAM instream, int ttl, char **body, - gpgme_data_t attestation, HWND hwnd, int preview_mode) -{ - gpg_error_t err; - struct gpgme_data_cbs cbs; - gpgme_data_t plaintext; - pgpmime_context_t ctx; - char *tmp; - - *body = NULL; - - memset (&cbs, 0, sizeof cbs); - cbs.write = plaintext_handler; - - ctx = xcalloc (1, sizeof *ctx + LINEBUFSIZE); - ctx->linebufsize = LINEBUFSIZE; - ctx->hwnd = hwnd; - ctx->preview = preview_mode; - - ctx->msg = rfc822parse_open (message_cb, ctx); - if (!ctx->msg) - { - err = gpg_error_from_errno (errno); - log_error ("failed to open the RFC822 parser: %s", strerror (errno)); - goto leave; - } - - err = gpgme_data_new_from_cbs (&plaintext, &cbs, ctx); - if (err) - goto leave; - - tmp = native_to_utf8 (_("[PGP/MIME message]")); - err = op_decrypt_stream_to_gpgme (GPGME_PROTOCOL_OpenPGP, - instream, plaintext, ttl, tmp, - attestation, preview_mode); - xfree (tmp); - if (!err && (ctx->parser_error || ctx->line_too_long)) - err = gpg_error (GPG_ERR_GENERAL); - - if (!err) - { - if (ctx->body) - { - /* Return the buffer but first make sure it is a string. */ - if (gpgme_data_write (ctx->body, "", 1) == 1) - { - *body = gpgme_data_release_and_get_mem (ctx->body, NULL); - ctx->body = NULL; - } - } - else - { - *body = native_to_utf8 (_("[PGP/MIME message " - "without plain text body]")); - } - } - - leave: - if (plaintext) - gpgme_data_release (plaintext); - if (ctx) - { - if (ctx->outstream) - { - IStream_Revert (ctx->outstream); - IStream_Release (ctx->outstream); - } - rfc822parse_close (ctx->msg); - if (ctx->body) - gpgme_data_release (ctx->body); - xfree (ctx->filename); - xfree (ctx); - } - return err; -} - - - -int -pgpmime_verify (const char *message, int ttl, char **body, - gpgme_data_t attestation, HWND hwnd, int preview_mode) -{ - gpg_error_t err = 0; - pgpmime_context_t ctx; - const char *s; - - *body = NULL; - - ctx = xcalloc (1, sizeof *ctx + LINEBUFSIZE); - ctx->linebufsize = LINEBUFSIZE; - ctx->hwnd = hwnd; - ctx->preview = preview_mode; - ctx->verify_mode = 1; - - ctx->msg = rfc822parse_open (message_cb, ctx); - if (!ctx->msg) - { - err = gpg_error_from_errno (errno); - log_error ("failed to open the RFC822 parser: %s", strerror (errno)); - goto leave; - } - - /* Need to pass the data line by line to the handler. */ - for (;(s = strchr (message, '\n')); message = s+1) - { - plaintext_handler (ctx, message, (s - message) + 1); - if (ctx->parser_error || ctx->line_too_long) - { - err = gpg_error (GPG_ERR_GENERAL); - break; - } - } - - /* Unless there is an error we should return the body. */ - if (!err) - { - if (ctx->body) - { - /* Return the buffer but first make sure it is a string. */ - if (gpgme_data_write (ctx->body, "", 1) == 1) - { - *body = gpgme_data_release_and_get_mem (ctx->body, NULL); - ctx->body = NULL; - } - } - else - { - *body = native_to_utf8 (_("[PGP/MIME signed message without a " - "plain text body]")); - } - } - - /* Now actually verify the signature. */ - if (!err && ctx->signed_data && ctx->sig_data) - { - char *tmp; - - gpgme_data_seek (ctx->signed_data, 0, SEEK_SET); - gpgme_data_seek (ctx->sig_data, 0, SEEK_SET); - tmp = native_to_utf8 (_("[PGP/MIME signature]")); - err = op_verify_detached_sig_gpgme (GPGME_PROTOCOL_OpenPGP, - ctx->signed_data, ctx->sig_data, - tmp, attestation); - xfree (tmp); - } - - - leave: - if (ctx) - { - if (ctx->outstream) - { - IStream_Revert (ctx->outstream); - IStream_Release (ctx->outstream); - } - rfc822parse_close (ctx->msg); - if (ctx->body) - gpgme_data_release (ctx->body); - if (ctx->signed_data) - gpgme_data_release (ctx->signed_data); - if (ctx->sig_data) - gpgme_data_release (ctx->sig_data); - xfree (ctx->filename); - xfree (ctx); - } - return err; -} diff --git a/src/pgpmime.h b/src/pgpmime.h deleted file mode 100644 index 5270368..0000000 --- a/src/pgpmime.h +++ /dev/null @@ -1,43 +0,0 @@ -/* pgpmime.h - PGP/MIME routines for Outlook - * Copyright (C) 2005 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 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ -#ifndef PGPMIME_H -#define PGPMIME_H - -#error not used - -#ifdef __cplusplus -extern "C" { -#if 0 -} -#endif -#endif - -int pgpmime_decrypt (LPSTREAM instream, int ttl, char **body, - gpgme_data_t attestation, HWND hwnd, - int preview_mode); -int pgpmime_verify (const char *message, int ttl, char **body, - gpgme_data_t attestation, HWND hwnd, int preview_mode); - - -#ifdef __cplusplus -} -#endif -#endif /*PGPMIME_H*/ diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp index fa10c8c..3eda852 100644 --- a/src/ribbon-callbacks.cpp +++ b/src/ribbon-callbacks.cpp @@ -1,763 +1,766 @@ /* ribbon-callbacks.h - Callbacks for the ribbon extension interface * Copyright (C) 2013 Intevation GmbH * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ribbon-callbacks.h" #include "gpgoladdin.h" #include "common.h" #include "mymapi.h" #include "mymapitags.h" #include "common.h" #include "mapihelp.h" #include "mimemaker.h" #include "filetype.h" #include "mail.h" #include #include +#undef _ +#define _(a) utf8_gettext (a) + using namespace GpgME; /* This is so super stupid. I bet even Microsft developers laugh about the definition of VARIANT_BOOL. And then for COM we have to pass pointers to this stuff. */ static VARIANT_BOOL var_true = VARIANT_TRUE; static VARIANT_BOOL var_false = VARIANT_FALSE; /* Gets the context of a ribbon control. And prints some useful debug output */ HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context) { *context = get_oom_object (ctrl, "get_Context"); if (*context) { char *name = get_object_name (*context); log_debug ("%s:%s: contextObj: %s", SRCNAME, __func__, name); xfree (name); } return context ? S_OK : E_FAIL; } /* getIcon Loads a PNG image from the resurce converts it into a Bitmap and Wraps it in an PictureDispatcher that is returned as result. Based on documentation from: http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI */ HRESULT getIcon (int id, VARIANT* result) { PICTDESC pdesc; LPDISPATCH pPict; HRESULT hr; Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::Bitmap* pbitmap; ULONG_PTR gdiplusToken; HRSRC hResource; DWORD imageSize; const void* pResourceData; HGLOBAL hBuffer; memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; if (!result) { log_error ("getIcon called without result variant."); return E_POINTER; } /* Initialize GDI */ gdiplusStartupInput.DebugEventCallback = NULL; gdiplusStartupInput.SuppressBackgroundThread = FALSE; gdiplusStartupInput.SuppressExternalCodecs = FALSE; gdiplusStartupInput.GdiplusVersion = 1; GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL); /* Get the image from the resource file */ hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA); if (!hResource) { log_error ("%s:%s: failed to find image: %i", SRCNAME, __func__, id); return E_FAIL; } imageSize = SizeofResource (glob_hinst, hResource); if (!imageSize) return E_FAIL; pResourceData = LockResource (LoadResource(glob_hinst, hResource)); if (!pResourceData) { log_error ("%s:%s: failed to load image: %i", SRCNAME, __func__, id); return E_FAIL; } hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize); if (hBuffer) { void* pBuffer = GlobalLock (hBuffer); if (pBuffer) { IStream* pStream = NULL; CopyMemory (pBuffer, pResourceData, imageSize); if (CreateStreamOnHGlobal (hBuffer, TRUE, &pStream) == S_OK) { memdbg_addRef (pStream); pbitmap = Gdiplus::Bitmap::FromStream (pStream); gpgol_release (pStream); if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap)) { log_error ("%s:%s: failed to get PNG.", SRCNAME, __func__); } } } GlobalUnlock (pBuffer); } GlobalFree (hBuffer); Gdiplus::GdiplusShutdown (gdiplusToken); /* Wrap the image into an OLE object. */ hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, TRUE, (void **) &pPict); if (hr != S_OK || !pPict) { log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n", SRCNAME, __func__, hr); return -1; } result->pdispVal = pPict; result->vt = VT_DISPATCH; return S_OK; } HRESULT mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer) { LPMESSAGE message = NULL; int oldflags, newflags; log_debug ("%s:%s: enter", SRCNAME, __func__); LPDISPATCH context = NULL; if (FAILED(getContext (ctrl, &context))) { TRACEPOINT; return E_FAIL; } LPDISPATCH mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); gpgol_release (context); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); return E_FAIL; } /* Get the uid of this item. */ char *uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); } } Mail *mail = nullptr; if (uid) { mail = Mail::getMailForUUID (uid); xfree (uid); } if (mail) { mail->setCryptoSelectedManually (true); } else { log_debug ("%s:%s: Failed to get mail object.", SRCNAME, __func__); } message = get_oom_base_message (mailitem); gpgol_release (mailitem); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); return S_OK; } oldflags = get_gpgol_draft_info_flags (message); if (flags == 3 && oldflags != 3) { // If only one sub button is active activate // both now. newflags = 3; } else { newflags = oldflags xor flags; } if (set_gpgol_draft_info_flags (message, newflags)) { log_error ("%s:%s: Failed to set draft flags.", SRCNAME, __func__); } gpgol_release (message); /* We need to invalidate the UI to update the toggle states of the subbuttons and the top button. Yeah, we invalidate a lot *sigh* */ gpgoladdin_invalidate_ui (); if (newflags & 1) { Mail::locateAllCryptoRecipients_o (); } return S_OK; } /* Get the state of encrypt / sign toggle buttons. flag values: 1 get the state of the encrypt button. 2 get the state of the sign button. If is_explorer is set to true we look at the inline response. */ HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result, bool is_explorer) { HRESULT hr; bool value; LPDISPATCH context = NULL, mailitem = NULL; LPMESSAGE message = NULL; result->vt = VT_BOOL | VT_BYREF; result->pboolVal = &var_false; /* First the usual defensive check about our parameters */ if (!ctrl || !result) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return E_FAIL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return E_FAIL; } mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); goto done; } message = get_oom_base_message (mailitem); if (!message) { log_error ("%s:%s: No message found.", SRCNAME, __func__); goto done; } value = (get_gpgol_draft_info_flags (message) & flags) == flags; result->pboolVal = value ? &var_true: &var_false; done: gpgol_release (context); gpgol_release (mailitem); gpgol_release (message); return S_OK; } static Mail * get_mail_from_control (LPDISPATCH ctrl, bool *none_selected) { HRESULT hr; LPDISPATCH context = NULL, mailitem = NULL; *none_selected = false; if (!ctrl) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return NULL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return NULL; } char *name = get_object_name (context); std::string ctx_name; if (name) { ctx_name = name; xfree (name); } if (ctx_name.empty()) { log_error ("%s:%s: Failed to get context name", SRCNAME, __func__); gpgol_release (context); return NULL; } if (!strcmp (ctx_name.c_str(), "_Inspector")) { mailitem = get_oom_object (context, "CurrentItem"); } else if (!strcmp (ctx_name.c_str(), "_Explorer")) { /* Avoid showing wrong crypto state if we don't have a reading pane. In that case the parser will finish for a mail which is gone and the crypto state will not get updated. */ if (0 /*g_ol_version_major >= 16 */) { /* Some Versions of Outlook 2016 crashed when accessing the current view of the Explorer. This was even reproducible with GpgOL disabled and only with Outlook Spy active. If you selected the explorer of an Outlook.com resource and then access the CurrentView and close the CurrentView again in Outlook Spy outlook crashes. See: T3484 The crash no longer occured at least since build 10228. As I'm not sure which Version fixed the crash we don't do a version check and just use the same codepath as for Outlook 2010 and 2013 again. Accessing PreviewPane.WordEditor is not a good solution here as it requires "Microsoft VBA for Office" (See T4056 ). A possible solution for that might be to check if "Mail.GetInspector().WordEditor()" returns NULL. In that case we know that we also won't get a WordEditor in the preview pane. */ LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor"); gpgol_release (prevEdit); if (!prevEdit) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } else { // Preview Pane is not available in older outlooks LPDISPATCH tableView = get_oom_object (context, "CurrentView"); if (!tableView) { // Woops, should not happen. TRACEPOINT; *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } else { int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane"); gpgol_release (tableView); if (!hasReadingPane) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } } if (!*none_selected) { /* Accessing the selection item can trigger a load event so we only do this here if we think that there might be something visible / selected. To avoid triggering a load if there is no content shown. */ LPDISPATCH selection = get_oom_object (context, "Selection"); if (!selection) { log_error ("%s:%s: Failed to get selection.", SRCNAME, __func__); gpgol_release (context); return NULL; } int count = get_oom_int (selection, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (selection, "Item(1)"); } gpgol_release (selection); if (!mailitem) { *none_selected = true; } } } else if (!strcmp (ctx_name.c_str(), "Selection")) { int count = get_oom_int (context, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (context, "Item(1)"); } if (!mailitem) { *none_selected = true; } } gpgol_release (context); if (!mailitem) { log_debug ("%s:%s: No mailitem. From %s", SRCNAME, __func__, ctx_name.c_str()); return NULL; } char *uid; /* Get the uid of this item. */ uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); gpgol_release (mailitem); return NULL; } } auto ret = Mail::getMailForUUID (uid); xfree (uid); if (!ret) { log_error ("%s:%s: Failed to find mail %p in map.", SRCNAME, __func__, mailitem); } gpgol_release (mailitem); return ret; } /* Helper to reduce code duplication.*/ #define MY_MAIL_GETTER \ if (!ctrl) \ { \ log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \ return E_FAIL; \ } \ bool none_selected; \ const auto mail = get_mail_from_control (ctrl, &none_selected); \ (void)none_selected; \ if (!mail) \ { \ log_oom ("%s:%s:%i Failed to get mail", \ SRCNAME, __func__, __LINE__); \ } HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (!result) { TRACEPOINT; return S_OK; } result->vt = VT_BOOL | VT_BYREF; result->pboolVal = none_selected ? &var_false : &var_true; TRACEPOINT; return S_OK; } HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (!mail) { log_debug ("%s:%s: No mail.", SRCNAME, __func__); w_result = utf8_to_wchar (_("Insecure")); result->bstrVal = SysAllocString (w_result); xfree (w_result); return S_OK; } w_result = utf8_to_wchar (mail->getCryptoSummary ().c_str ()); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (mail) { w_result = utf8_to_wchar (mail->getCryptoOneLine ().c_str()); } else if (!none_selected) { w_result = utf8_to_wchar (_("Insecure message")); } else { w_result = utf8_to_wchar (_("No message selected")); } result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; if (none_selected) { result->bstrVal = SysAllocString (L""); TRACEPOINT; return S_OK; } if (!mail || !mail->isCryptoMail ()) { wchar_t *w_result; w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, " "modified and read the message in transit.\n\n" "Click here to learn more.")); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } const auto message = mail->getCryptoDetails_o (); wchar_t *w_message = utf8_to_wchar (message.c_str()); result->bstrVal = SysAllocString (w_message); xfree (w_message); TRACEPOINT; return S_OK; } HRESULT launch_cert_details (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail || (!mail->isSigned () && !mail->isEncrypted ())) { ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/infographic", 0, 0, SW_SHOWNORMAL); return S_OK; } if (!mail->isSigned () && mail->isEncrypted ()) { /* Encrypt only, no information but show something. because we want the button to be active. Aheinecke: I don't think we should show to which keys the message is encrypted here. This would confuse users if they see keyids of unknown keys and the information can't be "true" because the sender could have sent the same information to other people or used throw keyids etc. */ char * buf; gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n" "There is no additional information available if it " "was actually sent by '%s' or if someone faked the sender address."), mail->getSender_o ().c_str()); MessageBox (NULL, buf, _("GpgOL"), MB_ICONINFORMATION|MB_OK); xfree (buf); return S_OK; } if (!mail->getSigFpr ()) { std::string buf = _("There was an error verifying the signature.\n" "Full details:\n"); buf += mail->getVerificationResultDump (); gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK); } char *uiserver = get_uiserver_name (); bool showError = false; if (uiserver) { std::string path (uiserver); xfree (uiserver); if (path.find("kleopatra.exe") != std::string::npos) { size_t dpos; if ((dpos = path.find(" --daemon")) != std::string::npos) { path.erase(dpos, strlen(" --daemon")); } auto ctx = Context::createForEngine(SpawnEngine); if (!ctx) { log_error ("%s:%s: No spawn engine.", SRCNAME, __func__); } std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ()); const char *argv[] = {path.c_str(), "--query", mail->getSigFpr (), "--parent-windowid", parentWid.c_str(), NULL }; log_debug ("%s:%s: Starting %s %s %s", SRCNAME, __func__, path.c_str(), argv[1], argv[2]); Data d(Data::null); ctx->spawnAsync(path.c_str(), argv, d, d, d, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); } else { showError = true; } } else { showError = true; } if (showError) { MessageBox (NULL, _("Could not find Kleopatra.\n" "Please reinstall Gpg4win with the Kleopatra component enabled."), _("GpgOL"), MB_ICONINFORMATION|MB_OK); } return S_OK; } HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (mail) { TRACEPOINT; return getIcon (mail->getCryptoIconID (), result); } TRACEPOINT; return getIcon (IDI_LEVEL_0, result); } HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BOOL | VT_BYREF; result->pboolVal = mail && (mail->isSigned () || mail->isEncrypted ()) ? &var_true : &var_false; TRACEPOINT; return S_OK; } HRESULT print_decrypted (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail) { log_error ("%s:%s: Failed to get mail.", SRCNAME, __func__); return S_OK; } invoke_oom_method (mail->item(), "PrintOut", NULL); return S_OK; }