Page MenuHome GnuPG

No OneTemporary

diff --git a/src/Makefile.am b/src/Makefile.am
index 93312cd..ef859bf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,133 +1,134 @@
# Makefile.am - main makefile for dialogs part of GPGol
# Copyright (C) 2005 g10 Code 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
bin_PROGRAMS = gpgol
#treeview
EXTRA_DIST = \
versioninfo.rc.in mapi32.def $(unused_sources) Outlook.gpl \
encrypt-16.bmp encrypt-16m.bmp \
encrypt-32.bmp encrypt-32m.bmp \
sign-16.bmp sign-16m.bmp \
sign-32.bmp sign-32m.bmp \
key-manager-16.bmp key-manager-16m.bmp \
key-manager-32.bmp key-manager-32m.bmp \
key-manager-64.bmp key-manager-64m.bmp \
decrypt-16.bmp decrypt-16m.bmp \
decrypt-32.bmp decrypt-32m.bmp \
verify-16.bmp verify-16m.bmp \
verify-32.bmp verify-32m.bmp \
decrypt-verify-16.bmp decrypt-verify-16m.bmp \
decrypt-verify-32.bmp decrypt-verify-32m.bmp \
encrypt-16.png encrypt-48.png \
key-manager-64.png \
decrypt-16.png decrypt-48.png \
encrypt-sign-file-48.png \
sign-48.png verify-48.png \
logo.bmp README.icons
EXEEXT = .dll
AM_CFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared
AM_CXXFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared
gpgol_SOURCES = \
main.c gpgol.def \
resource.rc \
gpgol-ids.h \
olflange.cpp olflange.h \
olflange-def.h \
olflange-dlgs.cpp \
dialogs.rc dialogs.h \
myexchext.h \
display.cpp display.h \
message.cpp message.h \
revert.cpp revert.h \
mimeparser.c mimeparser.h \
mimemaker.c mimemaker.h \
msgcache.c msgcache.h \
engine.c engine.h \
engine-assuan.c engine-assuan.h \
engine-gpgme.c engine-gpgme.h \
rfc822parse.c rfc822parse.h \
common.h common.c util.h \
xmalloc.h \
passcache.c passcache.h \
config-dialog.c \
passphrase-dialog.c \
recipient-dialog.c \
verify-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
+ windowmessages.h windowmessages.cpp \
+ gpgolstr.h gpgolstr.cpp
#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
libmapi32.a: mapi32.def
$(DLLTOOL) --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 .
clean-local:
rm -f libmapi32.a libgpg-error.a libgpgme.a libassuan.a
gpgol_LDFLAGS = -static-libgcc -static-libstdc++
gpgol_LDADD = $(srcdir)/gpgol.def \
-L . -lgpgme -lassuan -lgpg-error \
-lmapi32 -lshell32 -lgdi32 -lcomdlg32 \
-lole32 -loleaut32 -lws2_32 -ladvapi32 \
-luuid -lgdiplus
resource.o: resource.rc versioninfo.rc dialogs.rc
.rc.o:
$(WINDRES) -I $(srcdir) -I . `test -f '$<' || echo '$(srcdir)/'`$< $@
diff --git a/src/display.cpp b/src/display.cpp
index c81d78c..bd10921 100644
--- a/src/display.cpp
+++ b/src/display.cpp
@@ -1,476 +1,477 @@
/* display.cpp - Helper functions to display messages.
* Copyright (C) 2005, 2007 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include <windows.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "myexchext.h"
#include "common.h"
#include "mapihelp.h"
#include "olflange-def.h"
#include "display.h"
+#include "gpgolstr.h"
/* Check wether the string BODY is HTML formatted. */
int
is_html_body (const char *body)
{
char *p1, *p2;
/* XXX: it is possible but unlikely that the message text
contains the used keywords. */
p1 = strstr (body, "<HTML>");
p2 = strstr (body, "</HTML>");
if (p1 && p2)
return 1;
p1 = strstr (body, "<html>");
p2 = strstr (body, "</html>");
if (p1 && p2)
return 1;
/* XXX: use case insentensive strstr version. */
return 0;
}
/* Create a new body from body with suitable line endings. Caller must
release the result. */
char *
add_html_line_endings (const char *body)
{
size_t count;
const char *s;
char *p, *result;
for (count=0, s = body; *s; s++)
if (*s == '\n')
count++;
result = (char*)xmalloc ((s - body) + count*10 + 1);
for (s=body, p = result; *s; s++ )
if (*s == '\n')
p = stpcpy (p, "&nbsp;<br>\n");
else
*p++ = *s;
*p = 0;
return result;
}
/* A helper object for find_message_window. */
struct find_message_window_state
{
int level;
int seen_32770:1;
int seen_afxwndw:1;
};
/* We need this to find the mailer window because we directly change
the text of the window instead of the MAPI object itself. */
static HWND
find_message_window (HWND parent, struct find_message_window_state *findstate)
{
HWND child;
if (!parent)
return NULL;
child = GetWindow (parent, GW_CHILD);
while (child)
{
char buf[1024+1];
HWND w;
size_t len;
/* OL 2003 SP1 German uses this class name for the main
inspector window. We hope that no other windows uses this
class name. As a fallback we keep on testing for PGP
strings, but this does not work for PGP/MIME or already
decrypted messages. */
len = GetClassName (child, buf, sizeof buf - 1);
// if (len)
// log_debug (" %*sgot class `%s'", 2 * findstate->level, "", buf);
if (!len)
;
else if (findstate->level && len >= 10
&& !strncmp (buf, "MsoCommand", 10))
{
/* We won't find anything below MsoCommand windows.
Ignoring them fixes a bug where we return a RichEdit20W
window which is actually a formatting drop down box or
something similar. Not sure whether the check for level
is required, but it won't harm and might help in case an
MsoCommand* is the top level.
An example of such a message hierarchy is:
got class `MsoCommandBarDock'
got class `MsoCommandBarDock'
got class `MsoCommandBarDock'
got class `MsoCommandBar'
got class `MsoCommandBar'
got class `RichEdit20W' <--- We don't want that
got class `MsoCommandBar'
got class `MsoCommandBarDock'
got class `AfxWndW'
got class `#32770'
got class `Static'
got class `Static'
got class `RichEdit20WPT'
got class `Static'
got class `RichEdit20WPT'
got class `Static'
got class `RichEdit20WPT'
got class `Static'
got class `RichEdit20WPT'
got class `Static'
got class `RichEdit20WPT'
got class `Static'
got class `Static'
got class `AfxWndA'
got class `Static'
got class `AfxWndW'
got class `Static'
got class `RichEdit20W' <--- We want this one
*/
break; /* Not found at this level. */
}
else if (findstate->level == 2 && !strcmp (buf, "#32770"))
{
/* An inspector window has the #32770 class window at level
2 whereas the preview window has it at level 4. (OL2003
SP2, German). */
findstate->seen_32770 = 1;
findstate->seen_afxwndw = 0;
}
else if (findstate->seen_afxwndw && !strcmp (buf, "AfxWndW"))
{
findstate->seen_afxwndw = 1;
}
else if (findstate->seen_32770 && findstate->seen_afxwndw
&& !strcmp (buf, "RichEdit20W"))
{
log_debug ("found window class `%s' at level %d",
"RichEdit20W", findstate->level);
return child;
}
findstate->level++;
w = find_message_window (child, findstate);
findstate->level--;
findstate->seen_32770 = 0; /* Only interested in windows below. */
findstate->seen_afxwndw = 0;
if (w)
return w;
child = GetNextWindow (child, GW_HWNDNEXT);
}
return NULL;
}
/* Returns true if the the current display (as described by HWND) is a
real inspector and not the preview window. This is not 100%
reliable. */
int
is_inspector_display (HWND hwnd)
{
struct find_message_window_state findstate;
int rc;
memset (&findstate, 0, sizeof findstate);
rc = !!find_message_window (hwnd, &findstate);
// if (!rc)
// log_window_hierarchy (hwnd, "The windows below hwnd %p", hwnd);
return rc;
}
/* Update the display with TEXT using the message MSG. Return 0 on
success. */
int
update_display (HWND hwnd, LPDISPATCH inspector, int is_sensitive,
bool is_html, const char *text)
{
HWND window;
struct find_message_window_state findstate;
(void)is_sensitive;
memset (&findstate, 0, sizeof findstate);
window = find_message_window (hwnd, &findstate);
if (window && !is_html)
{
const char *s;
log_debug ("%s:%s: updating display using handle %p\n",
SRCNAME, __func__, window);
/* Decide whether we need to use the Unicode version. */
for (s=text; *s && !(*s & 0x80); s++)
;
if (*s)
{
wchar_t *tmp = utf8_to_wchar (text);
SetWindowTextW (window, tmp);
xfree (tmp);
}
else
SetWindowTextA (window, text);
return 0;
}
else if (inspector && !opt.compat.no_oom_write)
{
int rc;
LPDISPATCH item;
log_debug ("%s:%s: updating display using OOM\n", SRCNAME, __func__);
item = get_oom_object (inspector, "get_CurrentItem");
if (item)
{
if (is_html)
{
/* Bug in OL 2002 and 2003 - as a workaround set the body
first to empty. */
put_oom_string (item, "Body", "");
}
rc = put_oom_string (item, is_html? "HTMLBody":"Body", text);
item->Release ();
}
else
rc = -1;
return rc;
}
else
{
log_error ("%s:%s: window handle not found for parent %p\n",
SRCNAME, __func__, hwnd);
log_window_hierarchy (hwnd, "this is the window hierachy:");
return -1;
}
}
/* Set the body of MESSAGE to STRING. Returns 0 on success or an
error code otherwise. */
#if 0 /* Not anymore used. */
int
set_message_body (LPMESSAGE message, const char *string, bool is_html)
{
HRESULT hr;
SPropValue prop;
//SPropTagArray proparray;
const char *s;
assert (message);
// if (!is_html)
// {
// prop.ulPropTag = PR_BODY_HTML_A;
// prop.Value.lpszA = "";
// hr = HrSetOneProp (message, &prop);
// }
/* Decide whether we need to use the Unicode version. */
for (s=string; *s && !(*s & 0x80); s++)
;
if (*s)
{
prop.ulPropTag = is_html? PR_BODY_HTML_W : PR_BODY_W;
prop.Value.lpszW = utf8_to_wchar (string);
hr = HrSetOneProp (message, &prop);
xfree (prop.Value.lpszW);
}
else /* Only plain ASCII. */
{
prop.ulPropTag = is_html? PR_BODY_HTML_A : PR_BODY_A;
prop.Value.lpszA = (CHAR*)string;
hr = HrSetOneProp (message, &prop);
}
if (hr != S_OK)
{
log_debug ("%s:%s: HrSetOneProp failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return gpg_error (GPG_ERR_GENERAL);
}
/* Note: we once tried to delete the RTF property here to avoid any
syncing mess and more important to make sure that no RTF rendered
plaintext is left over. The side effect of this was that the
entire PR_BODY got deleted too. */
return 0;
}
#endif /* Not anymore used. */
int
open_inspector (LPEXCHEXTCALLBACK peecb, LPMESSAGE message)
{
HRESULT hr;
LPMAPISESSION session;
ULONG token;
char *entryid, *store_entryid, *parent_entryid;
size_t entryidlen, store_entryidlen, parent_entryidlen;
LPMDB mdb;
LPMAPIFOLDER mfolder;
ULONG mtype;
LPMESSAGE message2;
hr = peecb->GetSession (&session, NULL);
if (FAILED (hr) )
{
log_error ("%s:%s: error getting session: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
entryid = mapi_get_binary_prop (message, PR_ENTRYID, &entryidlen);
if (!entryid)
{
log_error ("%s:%s: PR_ENTRYID missing\n", SRCNAME, __func__);
session->Release ();
return -1;
}
log_hexdump (entryid, entryidlen, "orig entryid=");
store_entryid = mapi_get_binary_prop (message, PR_STORE_ENTRYID,
&store_entryidlen);
if (!store_entryid)
{
log_error ("%s:%s: PR_STORE_ENTRYID missing\n", SRCNAME, __func__);
session->Release ();
xfree (entryid);
return -1;
}
parent_entryid = mapi_get_binary_prop (message, PR_PARENT_ENTRYID,
&parent_entryidlen);
if (!parent_entryid)
{
log_error ("%s:%s: PR_PARENT_ENTRYID missing\n", SRCNAME, __func__);
session->Release ();
xfree (store_entryid);
xfree (entryid);
return -1;
}
/* Open the message store by ourself. */
hr = session->OpenMsgStore (0, store_entryidlen, (LPENTRYID)store_entryid,
NULL, MAPI_BEST_ACCESS | MDB_NO_DIALOG,
&mdb);
if (FAILED (hr))
{
log_error ("%s:%s: OpenMsgStore failed: hr=%#lx\n",
SRCNAME, __func__, hr);
session->Release ();
xfree (parent_entryid);
xfree (store_entryid);
xfree (entryid);
return -1;
}
/* Open the parent folder. */
hr = mdb->OpenEntry (parent_entryidlen, (LPENTRYID)parent_entryid,
&IID_IMAPIFolder, MAPI_BEST_ACCESS,
&mtype, (IUnknown**)&mfolder);
if (FAILED (hr))
{
log_error ("%s:%s: OpenEntry failed: hr=%#lx\n",
SRCNAME, __func__, hr);
session->Release ();
xfree (parent_entryid);
xfree (store_entryid);
xfree (entryid);
mdb->Release ();
return -1;
}
log_debug ("%s:%s: mdb::OpenEntry succeeded type=%lx\n",
SRCNAME, __func__, mtype);
/* Open the message. */
hr = mdb->OpenEntry (entryidlen, (LPENTRYID)entryid,
&IID_IMessage, MAPI_BEST_ACCESS,
&mtype, (IUnknown**)&message2);
if (FAILED (hr))
{
log_error ("%s:%s: OpenEntry[folder] failed: hr=%#lx\n",
SRCNAME, __func__, hr);
session->Release ();
xfree (parent_entryid);
xfree (store_entryid);
xfree (entryid);
mdb->Release ();
mfolder->Release ();
return -1;
}
log_debug ("%s:%s: mdb::OpenEntry[message] succeeded type=%lx\n",
SRCNAME, __func__, mtype);
/* Prepare and display the form. */
hr = session->PrepareForm (NULL, message2, &token);
if (FAILED (hr))
{
log_error ("%s:%s: PrepareForm failed: hr=%#lx\n",
SRCNAME, __func__, hr);
session->Release ();
xfree (parent_entryid);
xfree (store_entryid);
xfree (entryid);
mdb->Release ();
mfolder->Release ();
message2->Release ();
return -1;
}
/* Message2 is now represented by TOKEN; we need to release it. */
message2->Release(); message2 = NULL;
hr = session->ShowForm (0,
- mdb, mfolder,
+ mdb, mfolder,
NULL, token,
- NULL,
+ NULL,
0,
0, 0,
- 0, "IPM.Note");
+ 0, GpgOLStr("IPM.Note"));
log_debug ("%s:%s: ShowForm result: hr=%#lx\n",
SRCNAME, __func__, hr);
session->Release ();
xfree (parent_entryid);
xfree (store_entryid);
xfree (entryid);
mdb->Release ();
mfolder->Release ();
return FAILED(hr)? -1:0;
}
diff --git a/src/gpgolstr.cpp b/src/gpgolstr.cpp
new file mode 100644
index 0000000..d24dd35
--- /dev/null
+++ b/src/gpgolstr.cpp
@@ -0,0 +1,60 @@
+/* gpgolstr.cpp - String helper class for Outlook API.
+ * Copyright (C) 2015 Intevation GmbH
+ *
+ * This file is part of GpgOL.
+ *
+ * GpgOL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GpgOL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "gpgolstr.h"
+#include "util.h"
+
+GpgOLStr::GpgOLStr(const char *str) :
+ m_utf8str(NULL), m_widestr(NULL)
+{
+ if (!str)
+ return;
+ m_utf8str = strdup (str);
+}
+
+GpgOLStr::GpgOLStr(const wchar_t *str) :
+ m_utf8str(NULL), m_widestr(NULL)
+{
+ if (!str)
+ return;
+ m_widestr = wcsdup (str);
+}
+
+GpgOLStr::~GpgOLStr()
+{
+ xfree (m_utf8str);
+ xfree (m_widestr);
+}
+
+GpgOLStr::operator char*()
+{
+ if (!m_utf8str && m_widestr)
+ {
+ m_utf8str = wchar_to_utf8_2 (m_widestr, wcslen (m_widestr));
+ }
+ return m_utf8str;
+}
+
+GpgOLStr::operator wchar_t*()
+{
+ if (!m_widestr && m_utf8str)
+ {
+ m_widestr = utf8_to_wchar2 (m_utf8str, strlen (m_utf8str));
+ }
+ return m_widestr;
+}
diff --git a/src/gpgolstr.h b/src/gpgolstr.h
new file mode 100644
index 0000000..5bd6d26
--- /dev/null
+++ b/src/gpgolstr.h
@@ -0,0 +1,44 @@
+/* gpgolstr.h - String helper class for Outlook API.
+ * Copyright (C) 2015 Intevation GmbH
+ *
+ * This file is part of GpgOL.
+ *
+ * GpgOL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GpgOL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+
+/* Small string wrapper that handles wchar / utf8 conversion and
+ can be used as a temporary object for an allocated string.
+ Modifying the char or wchar_t pointer directly results in
+ undefined behavior.
+ They are intended to be used for API calls that expect a
+ mutable string but are actually a constant.
+ */
+class GpgOLStr
+{
+public:
+ GpgOLStr() : m_utf8str(NULL), m_widestr(NULL) {}
+
+ GpgOLStr(const char *str);
+ GpgOLStr(const wchar_t *widestr);
+ ~GpgOLStr();
+
+ operator char* ();
+ operator wchar_t* ();
+
+private:
+ char *m_utf8str;
+ wchar_t *m_widestr;
+};
diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp
index 049f784..8460bcc 100644
--- a/src/mapihelp.cpp
+++ b/src/mapihelp.cpp
@@ -1,3235 +1,3238 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <windows.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "rfc822parse.h"
#include "serpent.h"
#include "mapihelp.h"
#include "parsetlv.h"
+#include "gpgolstr.h"
#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
#define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
SRCNAME, __func__, __LINE__); \
} while (0)
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, wchar_t *name, const char *func)
+create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func)
{
HRESULT hr;
LPSPropTagArray proparr = NULL;
- MAPINAMEID mnid, *pmnid;
+ MAPINAMEID mnid, *pmnid;
+ GpgOLStr propname(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 = name;
+ mnid.Kind.lpwstrName = propname;
pmnid = &mnid;
hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
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}};
+ GpgOLStr propname (L"Internet Charset Body");
int result;
memset (&mnid, 0, sizeof mnid);
mnid.lpguid = &guid;
mnid.ulKind = MNID_STRING;
- mnid.Kind.lpwstrName = L"Internet Charset Body";
+ 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_error ("%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;
}
/* 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)
{
hr = message->SaveChanges (flags);
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 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);
stream->Release ();
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);
stream->Release ();
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);
stream->Release ();
return NULL;
}
stream->Release ();
{
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. */
static char *
get_msgcls_from_pgp_lines (LPMESSAGE message)
{
HRESULT hr;
LPSTREAM stream;
STATSTG statInfo;
ULONG nread;
size_t nbytes;
char *body = NULL;
char *p;
char *msgcls = NULL;
ULONG tag;
int is_binary = 0;
hr = 0;
if (!get_internetcharsetbody_tag (message, &tag) )
{
hr = message->OpenProperty (tag, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (!hr)
is_binary = 1;
}
if (hr)
{
tag = PR_BODY;
hr = message->OpenProperty (tag, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
}
if (hr)
{
log_debug ("%s:%s: OpenProperty(%lx) failed: hr=%#lx",
SRCNAME, __func__, tag, hr);
return NULL;
}
hr = stream->Stat (&statInfo, STATFLAG_NONAME);
if (hr)
{
log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
stream->Release ();
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);
stream->Release ();
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);
stream->Release ();
return NULL;
}
stream->Release ();
if (!is_binary)
{
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))
{
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))
break; /* Text before the PGP message - don't take this as a
proper message. */
}
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);
mapitable->Release ();
return -1;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (n_attach != 1)
{
FreeProws (mapirows);
mapitable->Release ();
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)
stream->Release ();
if (att)
att->Release ();
FreeProws (mapirows);
mapitable->Release ();
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);
mapitable->Release ();
return NULL;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (n_attach != 1)
{
FreeProws (mapirows);
mapitable->Release ();
log_debug ("%s:%s: not just 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)
att->Release ();
FreeProws (mapirows);
mapitable->Release ();
return result;
}
/* 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);
if (ct)
{
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
if (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 (!strcmp (ct, "text/plain"))
{
newvalue = get_msgcls_from_pgp_lines (message);
}
else if (!strcmp (ct, "multipart/mixed"))
{
/* It is quite common to have a multipart/mixed mail with
separate encrypted PGP parts. Look at the body to
decide. */
newvalue = get_msgcls_from_pgp_lines (message);
}
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 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 (!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");
}
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"))
{
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;
}
/* 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)
{
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 (!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. */
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 (opt.enable_smime && 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) && PROP_TYPE (propval2->ulPropTag) == PT_STRING8
&& propval2->Value.lpszA && strcmp (propval2->Value.lpszA, s))
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 (!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
{
/* 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;
}
/* 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;
if (!message)
return xstrdup ("[no message]"); /* Ooops. */
hr = HrGetOneProp ((LPMAPIPROP)message, PR_SENDER_EMAIL_ADDRESS_W, &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;
}
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 )
{
const char *s = propval->Value.lpszA;
if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
{
s += 14;
if (!*s)
msgtype = MSGTYPE_GPGOL;
else if (!strcmp (s, ".MultipartSigned"))
msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
else if (!strcmp (s, ".MultipartEncrypted"))
msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
else if (!strcmp (s, ".OpaqueSigned"))
msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
else if (!strcmp (s, ".OpaqueEncrypted"))
msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
else if (!strcmp (s, ".ClearSigned"))
msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
else if (!strcmp (s, ".PGPMessage"))
msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
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] =='.'))
msgtype = MSGTYPE_SMIME;
}
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;
}
stream->Release ();
}
session->Release ();
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-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);
mapitable->Release ();
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);
mapitable->Release ();
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);
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);
att->Release ();
}
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)
mapitable->Release ();
for (pos=0; !table[pos].end_of_table; pos++)
{
xfree (table[pos].filename);
xfree (table[pos].content_type);
}
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__);
att->Release ();
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);
att->Release ();
return NULL;
}
if (r_attach)
*r_attach = att;
else
att->Release ();
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, int unprotect,
int *r_was_protected)
{
HRESULT hr;
LPSTREAM stream;
STATSTG statInfo;
ULONG nread;
char *buffer;
symenc_t symenc = NULL;
if (r_was_protected)
*r_was_protected = 0;
if (unprotect)
{
ULONG tag;
char *iv;
size_t ivlen;
if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag)
&& (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
{
symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
xfree (iv);
if (!symenc)
log_error ("%s:%s: can't open encryption context",
SRCNAME, __func__);
}
}
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);
stream->Release ();
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);
stream->Release ();
return NULL;
}
if (nread != statInfo.cbSize.QuadPart)
{
log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
xfree (buffer);
buffer = NULL;
}
stream->Release ();
if (buffer && symenc)
{
symenc_cfb_decrypt (symenc, buffer, buffer, nread);
if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
{
xfree (buffer);
buffer = native_to_utf8
(_("[The content of this message is not visible because it has "
"been decrypted by another Outlook session. Use the "
"\"decrypt/verify\" command to make it visible]"));
nread = strlen (buffer);
}
else
{
memmove (buffer, buffer+16, nread-16);
nread -= 16;
if (r_was_protected)
*r_was_protected = 1;
}
}
/* Make sure that the buffer is a C string. */
if (buffer)
buffer[nread] = 0;
symenc_close (symenc);
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, int unprotect,
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__);
att->Release ();
return NULL;
}
buffer = attach_to_buffer (att, r_nbytes, unprotect, NULL);
att->Release ();
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:
att->Release ();
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)
{
HRESULT hr;
LPSPropValue propval = NULL;
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;
hr = HrGetOneProp ((LPMAPIPROP)message,
PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: error getting the headers lines: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
{
/* As per rfc822, header lines must be plain ascii, so no need
to cope with unicode etc. */
log_error ("%s:%s: proptag=%#lx not supported\n",
SRCNAME, __func__, propval->ulPropTag);
MAPIFreeBuffer (propval);
return NULL;
}
header_lines = propval->Value.lpszA;
/* 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\n", SRCNAME, __func__);
MAPIFreeBuffer (propval);
return NULL;
}
while ((s = strchr (header_lines, '\n')))
{
length = (s - header_lines);
if (length && s[-1] == '\r')
length--;
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);
MAPIFreeBuffer (propval);
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;
}
/* Returns True if MESSAGE has a GpgOL Last Decrypted property and
that matches the current session. */
int
mapi_test_last_decrypted (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
int yes = 0;
if (get_gpgollastdecrypted_tag (message, &tag) )
goto leave; /* No. */
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
if (FAILED (hr))
goto leave; /* No. */
if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
&& propval->Value.bin.cb == 8
&& !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
yes = 1;
MAPIFreeBuffer (propval);
leave:
log_debug ("%s:%s: message decrypted during this session: %s\n",
SRCNAME, __func__, yes?"yes":"no");
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);
mapitable->Release ();
return -1;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (!n_attach)
{
FreeProws (mapirows);
mapitable->Release ();
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, 1, r_protected);
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;
}
}
att->Release ();
if (r_ishtml)
*r_ishtml = (bodytype == 2);
break;
}
att->Release ();
}
FreeProws (mapirows);
mapitable->Release ();
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);
mapitable->Release ();
return 0;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (!n_attach)
{
FreeProws (mapirows);
mapitable->Release ();
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)
{
att->Release ();
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;
}
att->Release ();
}
FreeProws (mapirows);
mapitable->Release ();
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 ();
outstream->Release ();
}
if (instream)
instream->Release ();
if (att)
att->Release ();
return result;
}
diff --git a/src/message.cpp b/src/message.cpp
index 38b095e..db3e1ff 100644
--- a/src/message.cpp
+++ b/src/message.cpp
@@ -1,1286 +1,1288 @@
/* message.cpp - Functions for message handling
* Copyright (C) 2006, 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <windows.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "myexchext.h"
#include "common.h"
#include "mapihelp.h"
#include "mimeparser.h"
#include "mimemaker.h"
#include "display.h"
#include "message.h"
+#include "gpgolstr.h"
#define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
SRCNAME, __func__, __LINE__); \
} while (0)
/* Wrapper around UlRelease with error checking. */
static void
ul_release (LPVOID punk, const char *func, int lnr)
{
ULONG res;
if (!punk)
return;
res = UlRelease (punk);
if (opt.enable_debug & DBG_MEMORY)
log_debug ("%s:%s:%d: UlRelease(%p) had %lu references\n",
SRCNAME, func, lnr, punk, res);
}
/* A helper function used by OnRead and OnOpen to dispatch the
message. If FORCE is true, the force flag is passed to the
verification or decryption. Returns:
0 = Message has not been processed by us.
1 = Message has been processed and was not encrypted.
2 = Message has been processed by us and was possibly encrypted.
*/
int
message_incoming_handler (LPMESSAGE message, HWND hwnd, bool force)
{
int retval = 0;
msgtype_t msgtype;
int pass = 0;
retry:
pass++;
msgtype = mapi_get_message_type (message);
switch (msgtype)
{
case MSGTYPE_UNKNOWN:
/* If this message has never passed our change message class
code it won't have an unknown msgtype _and_ no sig status
flag. Thus we look at the message class now and change it if
required. It won't get displayed correctly right away but a
latter decrypt command or when viewed a second time all has
been set. Note that we should have similar code for some
message classes in GpgolUserEvents:OnSelectionChange; but
there are a couple of problems. */
if (pass == 1 && !force && !mapi_has_sig_status (message)
&& !opt.disable_gpgol)
{
log_debug ("%s:%s: message class not yet checked - doing now\n",
SRCNAME, __func__);
if (mapi_change_message_class (message, 0))
goto retry;
}
break;
case MSGTYPE_SMIME:
if (pass == 1 && !force && opt.enable_smime && !opt.disable_gpgol)
{
log_debug ("%s:%s: message class not checked with smime enabled "
"- doing now\n", SRCNAME, __func__);
if (mapi_change_message_class (message, 0))
goto retry;
}
break;
case MSGTYPE_GPGOL:
log_debug ("%s:%s: ignoring unknown message of original SMIME class\n",
SRCNAME, __func__);
break;
case MSGTYPE_GPGOL_MULTIPART_SIGNED:
log_debug ("%s:%s: processing multipart signed message\n",
SRCNAME, __func__);
retval = 1;
message_verify (message, msgtype, force, hwnd);
break;
case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
log_debug ("%s:%s: processing multipart encrypted message\n",
SRCNAME, __func__);
retval = 2;
message_decrypt (message, msgtype, force, hwnd);
break;
case MSGTYPE_GPGOL_OPAQUE_SIGNED:
log_debug ("%s:%s: processing opaque signed message\n",
SRCNAME, __func__);
retval = 1;
message_verify (message, msgtype, force, hwnd);
break;
case MSGTYPE_GPGOL_CLEAR_SIGNED:
log_debug ("%s:%s: processing clear signed pgp message\n",
SRCNAME, __func__);
retval = 1;
message_verify (message, msgtype, force, hwnd);
break;
case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
log_debug ("%s:%s: processing opaque encrypted message\n",
SRCNAME, __func__);
retval = 2;
message_decrypt (message, msgtype, force, hwnd);
break;
case MSGTYPE_GPGOL_PGP_MESSAGE:
log_debug ("%s:%s: processing pgp message\n", SRCNAME, __func__);
retval = 2;
message_decrypt (message, msgtype, force, hwnd);
break;
}
return retval;
}
/* Common Code used by OnReadComplete and OnOpenComplete to display a
modified message. Returns true if the message was encrypted. */
bool
message_display_handler (LPMESSAGE message, LPDISPATCH inspector, HWND hwnd)
{
int err;
int ishtml, wasprotected = false;
char *body;
if (mapi_get_message_type (message) == MSGTYPE_GPGOL_CLEAR_SIGNED)
{
/* We used to display the clearsigned data in the processed
form, that is without the PGP lines and without the dash
escaping. However, this poses the problem that the user does
not notice that he is viewing a mail which was signed using a
deprecated method and - far worse - it might update the
PR_BODY and thus all signature information will get lost. Of
course we could save the body away first like we do it with
encrypted mails, but that is too much overhead and GpgOL will
always be required to show such a message, which contrdicts
the very reason of clearsigned messages. */
log_debug ("%s:%s: skipping display update for ClearSigned\n",
SRCNAME, __func__);
}
else
{
err = mapi_get_gpgol_body_attachment (message, &body, NULL,
&ishtml, &wasprotected);
if (!err && body)
{
update_display (hwnd, inspector, wasprotected, ishtml, body);
}
else
{
update_display (hwnd, NULL, 0, 0,
_("[Crypto operation failed - "
"can't show the body of the message]"));
}
xfree (body);
}
return !!wasprotected;
}
/* Helper for message_wipe_body_cruft. */
static void
do_wipe_body (LPMESSAGE message)
{
if (!mapi_delete_body_props (message, KEEP_OPEN_READWRITE))
log_debug ("%s:%s: body cruft removed", SRCNAME, __func__);
}
/* If the current message is an encrypted one remove the body
properties which might have come up due to OL internal
syncronization and a failing olDiscard feature. */
void
message_wipe_body_cruft (LPEXCHEXTCALLBACK eecb)
{
HRESULT hr;
LPMESSAGE message = NULL;
LPMDB mdb = NULL;
log_debug ("%s:%s: enter", SRCNAME, __func__);
hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
if (SUCCEEDED (hr))
{
switch (mapi_get_message_type (message))
{
case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
{
if (mapi_has_last_decrypted (message))
do_wipe_body (message);
else
log_debug_w32 (hr, "%s:%s: "
"error getting message decryption status",
SRCNAME, __func__);
}
break;
case MSGTYPE_GPGOL_PGP_MESSAGE:
{
/* In general we can't delete the body of a message if it
is an inline PGP encrypted message because the body
holds the ciphertext. However, while decrypting, we
take a copy of the body and work on that in future; if
this has been done we can delete the body. */
mapi_attach_item_t *table;
int found = 0;
int tblidx;
table = mapi_create_attach_table (message, 0);
if (table)
{
for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
&& table[tblidx].filename
&& !strcmp (table[tblidx].filename, PGPBODYFILENAME))
{
found = 1;
break;
}
}
mapi_release_attach_table (table);
if (found)
do_wipe_body (message);
}
break;
default:
break;
}
ul_release (message, __func__, __LINE__);
ul_release (mdb, __func__, __LINE__);
}
}
/* Display some information about MESSAGE. */
void
message_show_info (LPMESSAGE message, HWND hwnd)
{
char *msgcls = mapi_get_message_class (message);
char *sigstat = mapi_get_sig_status (message);
char *mimeinfo = mapi_get_mime_info (message);
size_t buflen;
char *buffer;
buflen = strlen (msgcls) + strlen (sigstat) + strlen (mimeinfo) + 200;
buffer = (char*)xmalloc (buflen+1);
snprintf (buffer, buflen,
_("Signature status: %s\n"
"Message class ..: %s\n"
"MIME structure .:\n"
"%s"),
sigstat,
msgcls,
mimeinfo);
MessageBox (hwnd, buffer, _("GpgOL - Message Information"),
MB_ICONINFORMATION|MB_OK);
xfree (buffer);
xfree (mimeinfo);
xfree (sigstat);
xfree (msgcls);
}
static void
show_message (HWND hwnd, const char *text)
{
MessageBox (hwnd, text, _("GpgOL"), MB_ICONINFORMATION|MB_OK);
}
/* Convert the clear signed message from INPUT into a PGP/MIME signed
message and return it in a new allocated buffer. OUTPUTLEN
received the valid length of that buffer; the buffer is guaranteed
to be Nul terminated. Note: Because we need to insert an empty
line to indicate the end of MIME headers, the signature won't
verify unless we tell the signature verification routine to skip
this first line. */
static char *
pgp_mime_from_clearsigned (LPSTREAM input, size_t *outputlen)
{
HRESULT hr;
STATSTG statinfo;
ULONG nread;
char *body = NULL;
char *p, *p0, *dest, *mark;
char boundary[BOUNDARYSIZE+1];
char top_header[200 + 2*BOUNDARYSIZE];
char sig_header[100 + BOUNDARYSIZE];
char end_header[10 + BOUNDARYSIZE];
size_t reserved_space;
int state;
*outputlen = 0;
/* Note that our parser does not make use of the micalg parameter. */
generate_boundary (boundary);
snprintf (top_header, sizeof top_header,
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/signed; boundary=\"%s\";\r\n"
" protocol=\"application/pgp-signature\"\r\n"
"\r\n"
"--%s\r\n\r\n", boundary, boundary);
snprintf (sig_header, sizeof sig_header,
"--%s\r\n"
"Content-Type: application/pgp-signature\r\n"
"\r\n", boundary);
snprintf (end_header, sizeof end_header,
"\r\n"
"--%s--\r\n", boundary);
reserved_space = (strlen (top_header) + strlen (sig_header)
+ strlen (end_header)+ 100);
hr = input->Stat (&statinfo, STATFLAG_NONAME);
if (hr)
{
log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
return NULL;
}
body = (char*)xmalloc (reserved_space
+ (size_t)statinfo.cbSize.QuadPart + 2);
hr = input->Read (body+reserved_space,
(size_t)statinfo.cbSize.QuadPart, &nread);
if (hr)
{
log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
xfree (body);
return NULL;
}
body[reserved_space + nread] = 0;
body[reserved_space + nread+1] = 0; /* Just in case this is
accidently an wchar_t. */
if (nread != statinfo.cbSize.QuadPart)
{
log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
xfree (body);
return NULL;
}
/* We do in place conversion. */
state = 0;
dest = NULL;
for (p=body+reserved_space; p && *p; p = (p=strchr (p+1, '\n'))? (p+1):NULL)
{
if (!state && !strncmp (p, "-----BEGIN PGP SIGNED MESSAGE-----", 34)
&& trailing_ws_p (p+34) )
{
dest = stpcpy (body, top_header);
state = 1;
}
else if (state == 1)
{
/* Wait for an empty line. */
if (trailing_ws_p (p))
state = 2;
}
else if (state == 2 && strncmp (p, "-----", 5))
{
/* Copy signed data. */
p0 = p;
if (*p == '-' && p[1] == ' ')
p +=2; /* Remove escaping. */
mark = NULL;
while (*p && *p != '\n')
{
if (*p == ' ' || *p == '\t' || *p == '\r')
{
if (!mark)
mark = dest;
}
else
mark = NULL;
*dest++ = *p++;
}
if (mark)
dest = mark;
if (*p == '\n')
{
if (p > p0 && p[-1] == '\r')
*dest++ = '\r';
*dest++ = '\n';
}
if (p > p0)
p--; /* Adjust so that the strchr (p+1, '\n') can work. */
}
else if (state == 2)
{
/* Armor line encountered. */
p0 = p;
if (strncmp (p, "-----BEGIN PGP SIGNATURE-----", 29)
|| !trailing_ws_p (p+29) )
log_debug ("%s:%s: invalid clear signed message\n",
SRCNAME, __func__);
state = 3;
dest = stpcpy (dest, sig_header);
while (*p && *p != '\n')
*dest++ = *p++;
if (*p == '\n')
{
if (p[-1] == '\r')
*dest++ = '\r';
*dest++ = '\n';
}
if (p > p0)
p--; /* Adjust so that the strchr (p+1, '\n') can work. */
}
else if (state == 3 && strncmp (p, "-----", 5))
{
/* Copy signature. */
p0 = p;
while (*p && *p != '\n')
*dest++ = *p++;
if (*p == '\n')
{
if (p[-1] == '\r')
*dest++ = '\r';
*dest++ = '\n';
}
if (p > p0)
p--; /* Adjust so that the strchr (p+1, '\n') can work. */
}
else if (state == 3)
{
/* Armor line encountered. */
p0 = p;
if (strncmp (p, "-----END PGP SIGNATURE-----", 27)
|| !trailing_ws_p (p+27) )
log_debug ("%s:%s: invalid clear signed message (no end)\n",
SRCNAME, __func__);
while (*p && *p != '\n')
*dest++ = *p++;
if (*p == '\n')
{
if (p[-1] == '\r')
*dest++ = '\r';
*dest++ = '\n';
}
dest = stpcpy (dest, end_header);
if (p > p0)
p--; /* Adjust so that the strchr (p+1, '\n') can work. */
break; /* Ah well, we can stop here. */
}
}
if (!dest)
{
xfree (body);
return NULL;
}
*dest = 0;
*outputlen = strlen (body);
return body;
}
/* Verify MESSAGE and update the attachments as required. MSGTYPE
should be the type of the message so that the fucntion can decide
what to do. With FORCE set the verification is done regardlessless
of a cached signature result. */
int
message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
{
mapi_attach_item_t *table = NULL;
LPSTREAM opaquestream = NULL;
int moss_idx = -1;
int i;
char *inbuf = NULL;
size_t inbuflen = 0;
protocol_t protocol = PROTOCOL_UNKNOWN;
int err;
int mimehack = 0;
switch (msgtype)
{
case MSGTYPE_GPGOL_MULTIPART_SIGNED:
case MSGTYPE_GPGOL_OPAQUE_SIGNED:
case MSGTYPE_GPGOL_CLEAR_SIGNED:
break;
case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
case MSGTYPE_GPGOL_PGP_MESSAGE:
log_debug ("%s:%s: message of type %d not expected",
SRCNAME, __func__, msgtype);
if (force)
show_message (hwnd, _("Signature verification of an encrypted message "
"is not possible."));
return -1; /* Should not be called for such a message. */
case MSGTYPE_GPGOL:
case MSGTYPE_SMIME:
case MSGTYPE_UNKNOWN:
log_debug ("%s:%s: message of type %d ignored",
SRCNAME, __func__, msgtype);
if (!force)
;
else if (msgtype == MSGTYPE_GPGOL)
show_message (hwnd, _("Signature verification of this "
"message class is not possible."));
else if (msgtype == MSGTYPE_SMIME)
show_message (hwnd, _("Signature verification of this "
"S/MIME message is not possible. Please check "
"that S/MIME processing has been enabled."));
else
show_message (hwnd, _("This message has no signature."));
return 0; /* Nothing to do. */
}
/* If a verification is forced, we set the cached signature status
first to "?" to mark that no verification has yet happened. If a
verification status has been set and the body attachment is
available we don't do a verification again. The need to check
for the body attachment is to avoid problems if that attachment
has accidently be deleted. */
if (force)
mapi_set_sig_status (message, "?");
else if (mapi_test_sig_status (message)
&& !mapi_get_gpgol_body_attachment (message, NULL,NULL,NULL,NULL))
return 0; /* Already checked that message. */
if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED)
{
/* PGP's clear signed messages are special: All is contained in
the body and thus there is no requirement for an
attachment. */
LPSTREAM rawstream;
rawstream = mapi_get_body_as_stream (message);
if (!rawstream)
return -1;
inbuf = pgp_mime_from_clearsigned (rawstream, &inbuflen);
rawstream->Release ();
if (!inbuf)
return -1;
protocol = PROTOCOL_OPENPGP;
mimehack = 1; /* Required for our made up PGP/MIME. */
}
else if (msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED)
{
/* S/MIME opaque signed message: The data is expected to be in
an attachment. */
table = mapi_create_attach_table (message, 0);
if (!table)
return -1; /* No attachment - this should not happen. */
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)
{
log_debug ("%s:%s: attachment for opaque signed S/MIME not found",
SRCNAME, __func__);
mapi_release_attach_table (table);
return -1;
}
opaquestream = mapi_get_attach_as_stream (message, table+i, NULL);
if (!opaquestream)
{
mapi_release_attach_table (table);
return -1; /* Problem getting the attachment. */
}
protocol = PROTOCOL_SMIME;
}
else
{
/* PGP/MIME or S/MIME stuff. */
table = mapi_create_attach_table (message, 0);
if (!table)
return -1; /* No attachment - this should not happen. */
for (i=0; !table[i].end_of_table; i++)
if (table[i].attach_type == ATTACHTYPE_MOSS)
{
moss_idx = i;
break;
}
if (moss_idx == -1 && !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). */
moss_idx = 0;
mapi_mark_moss_attach (message, table+0);
}
if (moss_idx == -1)
{
mapi_release_attach_table (table);
return -1; /* No original attachment - this should not happen. */
}
inbuf = mapi_get_attach (message, 0, table+0, &inbuflen);
if (!inbuf)
{
mapi_release_attach_table (table);
return -1; /* Problem getting the attachment. */
}
}
if (opaquestream)
err = mime_verify_opaque (protocol, opaquestream,
NULL, 0, message, hwnd, 0, 0);
else
err = mime_verify (protocol, inbuf, inbuflen, message, hwnd, 0, mimehack);
log_debug ("mime_verify%s returned %d", opaquestream? "_opaque":"", err);
if (err && opt.enable_debug)
{
char buf[200];
snprintf (buf, sizeof buf, "Verify failed (%s)", gpg_strerror (err));
MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
}
if (opaquestream)
opaquestream->Release ();
xfree (inbuf);
if (err)
{
char buf[200];
snprintf (buf, sizeof buf, "- %s", gpg_strerror (err));
mapi_set_sig_status (message, gpg_strerror (err));
}
else
mapi_set_sig_status (message, "! Good signature");
mapi_save_changes (message, KEEP_OPEN_READWRITE);
mapi_release_attach_table (table);
return 0;
}
/* Copy the MAPI body to a PGPBODY type attachment. */
static int
pgp_body_to_attachment (LPMESSAGE message)
{
HRESULT hr;
LPSTREAM instream;
ULONG newpos;
LPATTACH newatt = NULL;
SPropValue prop;
LPSTREAM outstream = NULL;
LPUNKNOWN punk;
+ GpgOLStr 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 = PGPBODYFILENAME;
+ 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;
}
outstream->Release ();
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;
}
newatt->Release ();
newatt = NULL;
hr = mapi_save_changes (message, KEEP_OPEN_READWRITE);
leave:
if (outstream)
{
outstream->Revert ();
outstream->Release ();
}
if (newatt)
newatt->Release ();
instream->Release ();
return hr? -1:0;
}
/* Decrypt MESSAGE, check signature and update the attachments as
required. MSGTYPE should be the type of the message so that the
function can decide what to do. With FORCE set the decryption is
done regardless whether it has already been done. */
int
message_decrypt (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
{
mapi_attach_item_t *table = NULL;
int part1_idx, part2_idx;
int tblidx;
int retval = -1;
LPSTREAM cipherstream;
gpg_error_t err, sig_err;
int is_opaque = 0;
protocol_t protocol;
LPATTACH saved_attach = NULL;
int need_saved_attach = 0;
int need_rfc822_parser = 0;
int is_simple_pgp = 0;
switch (msgtype)
{
case MSGTYPE_UNKNOWN:
case MSGTYPE_SMIME:
case MSGTYPE_GPGOL:
case MSGTYPE_GPGOL_OPAQUE_SIGNED:
case MSGTYPE_GPGOL_MULTIPART_SIGNED:
case MSGTYPE_GPGOL_CLEAR_SIGNED:
if (force)
show_message (hwnd, _("This message is not encrypted."));
return -1; /* Should not have been called for this. */
case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
break;
case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
is_opaque = 1;
break;
case MSGTYPE_GPGOL_PGP_MESSAGE:
break;
}
if (!force && mapi_test_last_decrypted (message))
return 0; /* Already decrypted this message once during this
session. No need to do it again. */
if (msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
{
/* PGP messages are special: All is contained in the body and
thus there would be no requirement for an attachment.
However, due to problems with Outlook overwriting the body of
the message after decryption, we need to save the body away
before decrypting it. We then always look for that original
body attachment or create one if it does not exist. */
part1_idx = -1;
table = mapi_create_attach_table (message, 0);
if (!table)
;
else
{
for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
&& table[tblidx].filename
&& !strcmp (table[tblidx].filename, PGPBODYFILENAME))
{
part1_idx = tblidx;
break;
}
}
if (part1_idx == -1)
{
mapi_release_attach_table (table);
if (pgp_body_to_attachment (message))
table = NULL;
else
table = mapi_create_attach_table (message, 0);
if (table)
{
for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
&& table[tblidx].filename
&& !strcmp (table[tblidx].filename, PGPBODYFILENAME))
{
part1_idx = tblidx;
break;
}
}
}
if (!table || part1_idx == -1)
{
log_debug ("%s:%s: problem copying the PGP inline encrypted message",
SRCNAME, __func__);
goto leave;
}
cipherstream = mapi_get_attach_as_stream (message, table+part1_idx,
NULL);
if (!cipherstream)
goto leave; /* Problem getting the attachment. */
protocol = PROTOCOL_OPENPGP;
need_rfc822_parser = 1;
is_simple_pgp = 1;
}
else
{
/* PGP/MIME or S/MIME stuff. */
table = mapi_create_attach_table (message, 0);
if (!table)
goto leave; /* No attachment - this should not happen. */
if (is_opaque)
{
/* S/MIME opaque encrypted message: We expect one
attachment. As we don't know wether we are called the
first time, we first try to find this attachment by
looking at all attachments. Only if this fails we
identify it by its order. */
part2_idx = -1;
for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
if (table[tblidx].attach_type == ATTACHTYPE_MOSSTEMPL)
{
/* This attachment has been generated by us in the
course of sending a new message. The content will
be multipart/signed because we used this to trick
out OL. We stop here and use this part for further
processing. */
part2_idx = tblidx;
need_rfc822_parser = 1;
break;
}
else if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
{
if (part2_idx == -1 && table[tblidx].content_type
&& (!strcmp (table[tblidx].content_type,
"application/pkcs7-mime")
|| !strcmp (table[tblidx].content_type,
"application/x-pkcs7-mime")))
part2_idx = tblidx;
}
if (part2_idx == -1 && tblidx >= 1)
{
/* We have 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/pkcs7-mime")
|| !strcmp (table[0].content_type,
"application/x-pkcs7-mime")))
part2_idx = 0;
if (part2_idx != -1)
mapi_mark_moss_attach (message, table+part2_idx);
}
if (part2_idx == -1)
{
log_debug ("%s:%s: this is not an S/MIME encrypted message",
SRCNAME, __func__);
goto leave;
}
protocol = PROTOCOL_SMIME;
}
else
{
/* 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. */
part1_idx = part2_idx = -1;
for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
{
if (part1_idx == -1 && table[tblidx].content_type
&& !strcmp (table[tblidx].content_type,
"application/pgp-encrypted"))
part1_idx = tblidx;
else if (part2_idx == -1 && table[tblidx].content_type
&& !strcmp (table[tblidx].content_type,
"application/octet-stream"))
part2_idx = tblidx;
}
if (part1_idx == -1 && part2_idx == -1 && tblidx >= 2)
{
/* 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);
}
}
if (part1_idx == -1 || part2_idx == -1
&& !table[0].end_of_table && table[1].end_of_table
&& table[0].attach_type == ATTACHTYPE_MOSS
&& table[0].filename
&& !strcmp (table[0].filename, MIMEATTACHFILENAME))
{
/* This is likely a PGP/MIME created by us. Due to the
way we created that message, the MAPI derived content
type is wrong and there is only one attachment
(gpgolXXX.dat). We simply assume that it is PGP/MIME
encrypted and pass it on to the mime parser. We also
keep the attachment open so that we can later set it
to hidden if not yet done. I can't remember whether
it is possible to set the hidden attribute when
creating the message - probably not. Thus we take
care of it here. */
log_debug ("%s:%s: "
"assuming self-created PGP/MIME encrypted message",
SRCNAME, __func__);
part2_idx = 0;
need_saved_attach = 1;
}
else if (part1_idx == -1 || part2_idx == -1)
{
log_debug ("%s:%s: this is not a PGP/MIME encrypted message",
SRCNAME, __func__);
goto leave;
}
protocol = PROTOCOL_OPENPGP;
}
cipherstream = mapi_get_attach_as_stream (message, table+part2_idx,
need_saved_attach?
&saved_attach : NULL );
if (!cipherstream)
goto leave; /* Problem getting the attachment. */
}
sig_err = gpg_error (GPG_ERR_NO_DATA);
err = mime_decrypt (protocol, cipherstream, message,
need_rfc822_parser, is_simple_pgp, hwnd, 0, &sig_err);
log_debug ("mime_decrypt returned %d (%s)", err, gpg_strerror (err));
if (err && opt.enable_debug)
{
char buf[200];
switch (gpg_err_code (err))
{
case GPG_ERR_NO_DATA:
/* The UI server already displayed a message. */
break;
default:
snprintf (buf, sizeof buf,
_("Decryption failed\n(%s)"), gpg_strerror (err));
MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
break;
}
}
else if (!err)
{
if (saved_attach)
mapi_set_attach_hidden (saved_attach);
if (gpg_err_code (sig_err) != GPG_ERR_NO_DATA)
{
/* Note: Saving the result of the signature in a property
will reveal that there is a signature inside the
encrypted message - however it does reveal only a
common assumption and thus it is acceptable to do
this. */
if (sig_err)
{
char buf[200];
snprintf (buf, sizeof buf, "- %s", gpg_strerror (sig_err));
mapi_set_sig_status (message, gpg_strerror (sig_err));
}
else
mapi_set_sig_status (message, "! Good signature");
mapi_save_changes (message, KEEP_OPEN_READWRITE);
}
}
cipherstream->Release ();
retval = 0;
leave:
if (saved_attach)
saved_attach->Release ();
mapi_release_attach_table (table);
return retval;
}
/* Return an array of strings with the recipients of the message. On
success a malloced array is returned containing allocated strings
for each recipient. The end of the array is marked by NULL.
Caller is responsible for releasing the array. On failure NULL is
returned. */
static char **
get_recipients (LPMESSAGE message)
{
static SizedSPropTagArray (1L, PropRecipientNum) = {1L, {PR_EMAIL_ADDRESS}};
HRESULT hr;
LPMAPITABLE lpRecipientTable = NULL;
LPSRowSet lpRecipientRows = NULL;
unsigned int rowidx;
LPSPropValue row;
char **rset;
int rsetidx;
if (!message)
return NULL;
hr = message->GetRecipientTable (0, &lpRecipientTable);
if (FAILED (hr))
{
log_debug_w32 (-1, "%s:%s: GetRecipientTable failed", SRCNAME, __func__);
return NULL;
}
hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
NULL, NULL, 0L, &lpRecipientRows);
if (FAILED (hr))
{
log_debug_w32 (-1, "%s:%s: HrQueryAllRows failed", SRCNAME, __func__);
if (lpRecipientTable)
lpRecipientTable->Release();
return NULL;
}
rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
for (rowidx=0, rsetidx=0; rowidx < lpRecipientRows->cRows; rowidx++)
{
if (!lpRecipientRows->aRow[rowidx].cValues)
continue;
row = lpRecipientRows->aRow[rowidx].lpProps;
switch (PROP_TYPE (row->ulPropTag))
{
case PT_UNICODE:
if ((rset[rsetidx] = wchar_to_utf8 (row->Value.lpszW)))
rsetidx++;
else
log_debug ("%s:%s: error converting recipient to utf8\n",
SRCNAME, __func__);
break;
case PT_STRING8: /* Assume ASCII. */
rset[rsetidx++] = xstrdup (row->Value.lpszA);
break;
default:
log_debug ("%s:%s: proptag=0x%08lx not supported\n",
SRCNAME, __func__, row->ulPropTag);
break;
}
}
if (lpRecipientTable)
lpRecipientTable->Release();
if (lpRecipientRows)
FreeProws(lpRecipientRows);
log_debug ("%s:%s: got %d recipients:\n", SRCNAME, __func__, rsetidx);
for (rsetidx=0; rset[rsetidx]; rsetidx++)
log_debug ("%s:%s: \t`%s'\n", SRCNAME, __func__, rset[rsetidx]);
return rset;
}
static void
release_recipient_array (char **recipients)
{
int idx;
if (recipients)
{
for (idx=0; recipients[idx]; idx++)
xfree (recipients[idx]);
xfree (recipients);
}
}
static int
sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd, int signflag)
{
gpg_error_t err;
char **recipients;
recipients = get_recipients (message);
if (!recipients || !recipients[0])
{
MessageBox (hwnd, _("No recipients to encrypt to are given"),
"GpgOL", MB_ICONERROR|MB_OK);
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
if (signflag)
err = mime_sign_encrypt (message, hwnd, protocol, recipients);
else
err = mime_encrypt (message, hwnd, protocol, recipients);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
MessageBox (hwnd, _("Encrypting or signing an empty message "
"is not possible."),
"GpgOL", MB_ICONERROR|MB_OK);
}
else if (err && opt.enable_debug)
{
char buf[200];
snprintf (buf, sizeof buf,
_("Encryption failed (%s)"), gpg_strerror (err));
MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
}
}
release_recipient_array (recipients);
return err;
}
/* Sign the MESSAGE. */
int
message_sign (LPMESSAGE message, protocol_t protocol, HWND hwnd)
{
gpg_error_t err;
err = mime_sign (message, hwnd, protocol);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
MessageBox (hwnd, _("Encrypting or signing an empty message "
"is not possible."),
"GpgOL", MB_ICONERROR|MB_OK);
}
else if (err && opt.enable_debug)
{
char buf[200];
snprintf (buf, sizeof buf,
_("Signing failed (%s)"), gpg_strerror (err));
MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
}
return err;
}
/* Encrypt the MESSAGE. */
int
message_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd)
{
return sign_encrypt (message, protocol, hwnd, 0);
}
/* Sign+Encrypt the MESSAGE. */
int
message_sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd)
{
return sign_encrypt (message, protocol, hwnd, 1);
}
diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp
index ade0330..882240e 100644
--- a/src/ribbon-callbacks.cpp
+++ b/src/ribbon-callbacks.cpp
@@ -1,1327 +1,1328 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <windows.h>
#include <olectl.h>
#include <stdio.h>
#include <string.h>
#include <gdiplus.h>
#include <objidl.h>
#include "ribbon-callbacks.h"
#include "gpgoladdin.h"
#include "util.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "myexchext.h"
#include "common.h"
#include "display.h"
#include "msgcache.h"
#include "engine.h"
#include "engine-assuan.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "filetype.h"
+#include "gpgolstr.h"
/* 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");
log_debug ("%s:%s: contextObj: %s",
SRCNAME, __func__, get_object_name (*context));
return context ? S_OK : E_FAIL;
}
#define OP_ENCRYPT 1 /* Encrypt the data */
#define OP_SIGN 2 /* Sign the data */
#define OP_DECRYPT 1 /* Decrypt the data */
#define OP_VERIFY 2 /* Verify the data */
#define DATA_BODY 4 /* Use text body as data */
#define DATA_SELECTION 8 /* Use selection as data */
/* Read hfile in chunks of 4KB and writes them to the sink */
static int
copyFileToSink (HANDLE hFile, sink_t sink)
{
char buf[4096];
DWORD bytesRead = 0;
do
{
if (!ReadFile (hFile, buf, sizeof buf, &bytesRead, NULL))
{
log_error ("%s:%s: Could not read source file.",
SRCNAME, __func__);
return -1;
}
if (write_buffer (sink, bytesRead ? buf : NULL, bytesRead))
{
log_error ("%s:%s: Could not write out buffer",
SRCNAME, __func__);
return -1;
}
}
while (bytesRead);
return 0;
}
static int
attachSignature (LPDISPATCH mailItem, char *subject, HANDLE hFileToSign,
protocol_t protocol, unsigned int session_number,
HWND curWindow, wchar_t *fileNameToSign, char *sender)
{
wchar_t *sigName = NULL;
wchar_t *sigFileName = NULL;
HANDLE hSigFile = NULL;
int rc = 0;
struct sink_s encsinkmem;
sink_t encsink = &encsinkmem;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
engine_filter_t filter = NULL;
memset (encsink, 0, sizeof *encsink);
memset (sink, 0, sizeof *sink);
/* Prepare a fresh filter */
if ((rc = engine_create_filter (&filter, write_buffer_for_cb, sink)))
{
goto failure;
}
encsink->cb_data = filter;
encsink->writefnc = sink_encryption_write;
engine_set_session_number (filter, session_number);
engine_set_session_title (filter, subject ? subject :_("GpgOL"));
if (engine_sign_start (filter, curWindow, protocol, sender, &protocol))
goto failure;
sigName = get_pretty_attachment_name (fileNameToSign, protocol, 1);
/* If we are unlucky the number of temporary file artifacts might
differ for the signature and the encrypted file but we have
to live with that. */
sigFileName = get_tmp_outfile (sigName, &hSigFile);
sink->cb_data = hSigFile;
sink->writefnc = sink_file_write;
if (!sigFileName)
{
log_error ("%s:%s: Could not get a decent attachment name",
SRCNAME, __func__);
goto failure;
}
/* Reset the file to sign handle to the beginning of the file and
copy it to the signature buffer */
SetFilePointer (hFileToSign, 0, NULL, 0);
if ((rc=copyFileToSink (hFileToSign, encsink)))
goto failure;
/* Lets hope the user did not select a huge file. We are hanging
here until encryption is completed.. */
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
encsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_error ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Now we have an encrypted file behind encryptedFile. Let's add it */
add_oom_attachment (mailItem, sigFileName);
failure:
xfree (sigFileName);
xfree (sigName);
if (hSigFile)
{
CloseHandle (hSigFile);
DeleteFileW (sigFileName);
}
return rc;
}
/* do_composer_action
Encrypts / Signs text in an IInspector context.
Depending on the flags either the
active selection or the full body is encrypted.
Combine OP_ENCRYPT and OP_SIGN if you want both.
*/
HRESULT
do_composer_action (LPDISPATCH ctrl, int flags)
{
LPDISPATCH context = NULL;
LPDISPATCH selection = NULL;
LPDISPATCH wordEditor = NULL;
LPDISPATCH application = NULL;
LPDISPATCH mailItem = NULL;
LPDISPATCH sender = NULL;
LPDISPATCH recipients = NULL;
struct sink_s encsinkmem;
sink_t encsink = &encsinkmem;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
char* senderAddr = NULL;
char** recipientAddrs = NULL;
LPSTREAM tmpstream = NULL;
engine_filter_t filter = NULL;
char* plaintext = NULL;
int rc = 0;
HRESULT hr;
HWND curWindow;
protocol_t protocol;
unsigned int session_number;
int i;
STATSTG tmpStat;
log_debug ("%s:%s: enter", SRCNAME, __func__);
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
memset (encsink, 0, sizeof *encsink);
memset (sink, 0, sizeof *sink);
curWindow = get_oom_context_window (context);
wordEditor = get_oom_object (context, "WordEditor");
application = get_oom_object (wordEditor, "get_Application");
selection = get_oom_object (application, "get_Selection");
mailItem = get_oom_object (context, "CurrentItem");
sender = get_oom_object (mailItem, "Session.CurrentUser");
recipients = get_oom_object (mailItem, "Recipients");
if (!wordEditor || !application || !selection || !mailItem ||
!sender || !recipients)
{
MessageBox (NULL,
"Internal error in GpgOL.\n"
"Could not find all objects.",
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
log_error ("%s:%s: Could not find all objects.",
SRCNAME, __func__);
goto failure;
}
if (flags & DATA_SELECTION)
{
plaintext = get_oom_string (selection, "Text");
if (!plaintext || strlen (plaintext) <= 1)
{
MessageBox (NULL,
_("Please select text to encrypt."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
}
else if (flags & DATA_BODY)
{
plaintext = get_oom_string (mailItem, "Body");
if (!plaintext || strlen (plaintext) <= 1)
{
MessageBox (NULL,
_("Textbody empty."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
}
/* Create a temporary sink to construct the encrypted data. */
hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
(SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
| STGM_CREATE | STGM_READWRITE),
- NULL, "GPG", &tmpstream);
+ NULL, GpgOLStr("GPG"), &tmpstream);
if (FAILED (hr))
{
log_error ("%s:%s: can't create temp file: hr=%#lx\n",
SRCNAME, __func__, hr);
rc = -1;
goto failure;
}
sink->cb_data = tmpstream;
sink->writefnc = sink_std_write;
/* Now lets prepare our encryption */
session_number = engine_new_session_number ();
/* Prepare the encryption sink */
if (engine_create_filter (&filter, write_buffer_for_cb, sink))
{
goto failure;
}
encsink->cb_data = filter;
encsink->writefnc = sink_encryption_write;
engine_set_session_number (filter, session_number);
engine_set_session_title (filter, _("GpgOL"));
senderAddr = get_pa_string (sender, PR_SMTP_ADDRESS);
if (flags & OP_ENCRYPT)
{
recipientAddrs = get_oom_recipients (recipients);
if (!recipientAddrs || !(*recipientAddrs))
{
MessageBox (NULL,
_("Please add at least one recipent."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
if ((rc=engine_encrypt_prepare (filter, curWindow,
PROTOCOL_UNKNOWN,
(flags & OP_SIGN) ?
ENGINE_FLAG_SIGN_FOLLOWS : 0,
senderAddr, recipientAddrs,
&protocol)))
{
log_error ("%s:%s: engine encrypt prepare failed : %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
if ((rc=engine_encrypt_start (filter, 0)))
{
log_error ("%s:%s: engine encrypt start failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
}
else
{
/* We could do some kind of clearsign / sign text as attachment here
but it is error prone */
if ((rc=engine_sign_opaque_start (filter, curWindow, PROTOCOL_UNKNOWN,
senderAddr, &protocol)))
{
log_error ("%s:%s: engine sign start failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
}
/* Write the text in the encryption sink. */
rc = write_buffer (encsink, plaintext, strlen (plaintext));
if (rc)
{
log_error ("%s:%s: writing tmpstream to encsink failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
/* Flush the encryption sink and wait for the encryption to get
ready. */
if ((rc = write_buffer (encsink, NULL, 0)))
goto failure;
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
encsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Check the size of the encrypted data */
tmpstream->Stat (&tmpStat, 0);
if (tmpStat.cbSize.QuadPart > UINT_MAX)
{
log_error ("%s:%s: No one should write so large mails.",
SRCNAME, __func__);
goto failure;
}
/* Copy the encrypted stream to the message editor. */
{
LARGE_INTEGER off;
ULONG nread;
char buffer[(unsigned int)tmpStat.cbSize.QuadPart + 1];
memset (buffer, 0, sizeof buffer);
off.QuadPart = 0;
hr = tmpstream->Seek (off, STREAM_SEEK_SET, NULL);
if (hr)
{
log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
hr = tmpstream->Read (buffer, sizeof (buffer) - 1, &nread);
if (hr)
{
log_error ("%s:%s: IStream::Read failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
if (strlen (buffer) > 1)
{
if (flags & OP_SIGN)
{
/* When signing we append the signature after the body */
unsigned int combinedSize = strlen (buffer) +
strlen (plaintext) + 5;
char combinedBody[combinedSize];
memset (combinedBody, 0, combinedSize);
snprintf (combinedBody, combinedSize, "%s\r\n\r\n%s", plaintext,
buffer);
if (flags & DATA_SELECTION)
put_oom_string (selection, "Text", combinedBody);
else if (flags & DATA_BODY)
put_oom_string (mailItem, "Body", combinedBody);
}
else if (protocol == PROTOCOL_SMIME)
{
unsigned int enclosedSize = strlen (buffer) + 34 + 31 + 1;
char enclosedData[enclosedSize];
snprintf (enclosedData, sizeof enclosedData,
"-----BEGIN ENCRYPTED MESSAGE-----\r\n"
"%s"
"-----END ENCRYPTED MESSAGE-----\r\n", buffer);
if (flags & DATA_SELECTION)
put_oom_string (selection, "Text", enclosedData);
else if (flags & DATA_BODY)
put_oom_string (mailItem, "Body", enclosedData);
}
else
{
if (flags & DATA_SELECTION)
put_oom_string (selection, "Text", buffer);
else if (flags & DATA_BODY)
{
put_oom_string (mailItem, "Body", buffer);
}
}
}
else
{
/* Just to be save not to overwrite the selection with
an empty buffer */
log_error ("%s:%s: unexpected problem ", SRCNAME, __func__);
goto failure;
}
}
failure:
if (rc)
log_debug ("%s:%s: failed rc=%d (%s) <%s>", SRCNAME, __func__, rc,
gpg_strerror (rc), gpg_strsource (rc));
engine_cancel (filter);
RELDISP(wordEditor);
RELDISP(application);
RELDISP(selection);
RELDISP(sender);
RELDISP(recipients);
RELDISP(mailItem);
RELDISP(tmpstream);
xfree (plaintext);
xfree (senderAddr);
if (recipientAddrs)
{
for (i=0; recipientAddrs && recipientAddrs[i]; i++)
xfree (recipientAddrs[i]);
xfree (recipientAddrs);
}
log_debug ("%s:%s: leave", SRCNAME, __func__);
return S_OK;
}
HRESULT
decryptAttachments (LPDISPATCH ctrl)
{
LPDISPATCH context = NULL;
LPDISPATCH attachmentSelection;
int attachmentCount;
HRESULT hr = 0;
int i = 0;
HWND curWindow;
int err;
hr = getContext(ctrl, &context);
attachmentSelection = get_oom_object (context, "AttachmentSelection");
if (!attachmentSelection)
{
/* We can be called from a context menu, in that case we
directly have an AttachmentSelection context. Otherwise
we have an Explorer context with an Attachment Selection property. */
attachmentSelection = context;
}
attachmentCount = get_oom_int (attachmentSelection, "Count");
curWindow = get_oom_context_window (context);
{
char *filenames[attachmentCount + 1];
filenames[attachmentCount] = NULL;
/* Yes the items start at 1! */
for (i = 1; i <= attachmentCount; i++)
{
char buf[16];
char *filename;
wchar_t *wcsOutFilename;
DISPPARAMS saveParams;
VARIANT aVariant[1];
LPDISPATCH attachmentObj;
DISPID saveID;
snprintf (buf, sizeof (buf), "Item(%i)", i);
attachmentObj = get_oom_object (attachmentSelection, buf);
if (!attachmentObj)
{
/* Should be impossible */
filenames[i-1] = NULL;
log_error ("%s:%s: could not find Item %i;",
SRCNAME, __func__, i);
break;
}
filename = get_oom_string (attachmentObj, "FileName");
saveID = lookup_oom_dispid (attachmentObj, "SaveAsFile");
saveParams.rgvarg = aVariant;
saveParams.rgvarg[0].vt = VT_BSTR;
filenames[i-1] = get_save_filename (NULL, filename);
xfree (filename);
if (!filenames [i-1])
continue;
wcsOutFilename = utf8_to_wchar2 (filenames[i-1],
strlen(filenames[i-1]));
saveParams.rgvarg[0].bstrVal = SysAllocString (wcsOutFilename);
saveParams.cArgs = 1;
saveParams.cNamedArgs = 0;
hr = attachmentObj->Invoke (saveID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &saveParams,
NULL, NULL, NULL);
SysFreeString (saveParams.rgvarg[0].bstrVal);
RELDISP (attachmentObj);
if (FAILED(hr))
{
int j;
log_debug ("%s:%s: Saving to file failed. hr: %x",
SRCNAME, __func__, (unsigned int) hr);
for (j = 0; j < i; j++)
xfree (filenames[j]);
RELDISP (attachmentSelection);
return hr;
}
}
RELDISP (attachmentSelection);
err = op_assuan_start_decrypt_files (curWindow, filenames);
for (i = 0; i < attachmentCount; i++)
xfree (filenames[i]);
}
log_debug ("%s:%s: Leaving. Err: %i",
SRCNAME, __func__, err);
return S_OK; /* If we return an error outlook will show that our
callback function failed in an ugly window. */
}
/* do_reader_action
decrypts the content of an inspector. Controled by flags
similary to the do_composer_action.
*/
HRESULT
do_reader_action (LPDISPATCH ctrl, int flags)
{
LPDISPATCH context = NULL;
LPDISPATCH selection = NULL;
LPDISPATCH wordEditor = NULL;
LPDISPATCH mailItem = NULL;
LPDISPATCH wordApplication = NULL;
struct sink_s decsinkmem;
sink_t decsink = &decsinkmem;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
LPSTREAM tmpstream = NULL;
engine_filter_t filter = NULL;
HWND curWindow;
char* encData = NULL;
char* senderAddr = NULL;
char* subject = NULL;
int encDataLen = 0;
int rc = 0;
unsigned int session_number;
HRESULT hr;
STATSTG tmpStat;
protocol_t protocol;
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
memset (decsink, 0, sizeof *decsink);
memset (sink, 0, sizeof *sink);
curWindow = get_oom_context_window (context);
if (!(flags & DATA_BODY))
{
wordEditor = get_oom_object (context, "WordEditor");
wordApplication = get_oom_object (wordEditor, "get_Application");
selection = get_oom_object (wordApplication, "get_Selection");
}
mailItem = get_oom_object (context, "CurrentItem");
if ((!wordEditor || !wordApplication || !selection || !mailItem) &&
!(flags & DATA_BODY))
{
MessageBox (NULL,
"Internal error in GpgOL.\n"
"Could not find all objects.",
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
log_error ("%s:%s: Could not find all objects.",
SRCNAME, __func__);
goto failure;
}
if (!mailItem)
{
/* This happens when we try to decrypt the body of a mail in the
explorer context. */
mailItem = get_oom_object (context, "Selection.Item(1)");
if (!mailItem)
{
MessageBox (NULL,
_("Please select a Mail."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
}
if (flags & DATA_SELECTION)
{
encData = get_oom_string (selection, "Text");
if (!encData || (encDataLen = strlen (encData)) <= 1)
{
MessageBox (NULL,
_("Please select the data you wish to decrypt."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
}
else if (flags & DATA_BODY)
{
encData = get_oom_string (mailItem, "Body");
if (!encData || (encDataLen = strlen (encData)) <= 1)
{
MessageBox (NULL,
_("Nothing to decrypt."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
}
fix_linebreaks (encData, &encDataLen);
subject = get_oom_string (mailItem, "Subject");
if (get_oom_bool (mailItem, "Sent"))
{
char *addrType = get_oom_string (mailItem, "SenderEmailType");
if (addrType && strcmp("SMTP", addrType) == 0)
{
senderAddr = get_oom_string (mailItem, "SenderEmailAddress");
}
else
{
/* Not SMTP, fall back to try getting the property. */
LPDISPATCH sender = get_oom_object (mailItem, "Sender");
senderAddr = get_pa_string (sender, PR_SMTP_ADDRESS);
RELDISP (sender);
}
xfree (addrType);
}
else
{
/* If the message has not been sent we might be composing
in this case use the current address */
LPDISPATCH sender = get_oom_object (mailItem, "Session.CurrentUser");
senderAddr = get_pa_string (sender, PR_SMTP_ADDRESS);
RELDISP (sender);
}
/* Determine the protocol based on the content */
protocol = is_cms_data (encData, encDataLen) ? PROTOCOL_SMIME :
PROTOCOL_OPENPGP;
hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
(SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
| STGM_CREATE | STGM_READWRITE),
- NULL, "GPG", &tmpstream);
+ NULL, GpgOLStr("GPG"), &tmpstream);
if (FAILED (hr))
{
log_error ("%s:%s: can't create temp file: hr=%#lx\n",
SRCNAME, __func__, hr);
rc = -1;
goto failure;
}
sink->cb_data = tmpstream;
sink->writefnc = sink_std_write;
session_number = engine_new_session_number ();
if (engine_create_filter (&filter, write_buffer_for_cb, sink))
goto failure;
decsink->cb_data = filter;
decsink->writefnc = sink_encryption_write;
engine_set_session_number (filter, session_number);
engine_set_session_title (filter, subject ? subject : _("GpgOL"));
if (flags & OP_DECRYPT)
{
if ((rc=engine_decrypt_start (filter, curWindow,
protocol,
1, NULL)))
{
log_error ("%s:%s: engine decrypt start failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
}
else if (flags & OP_VERIFY)
{
log_debug ("Starting verify");
if ((rc=engine_verify_start (filter, curWindow,
NULL, 0, protocol, senderAddr)))
{
log_error ("%s:%s: engine verify start failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
}
/* Write the text in the decryption sink. */
rc = write_buffer (decsink, encData, encDataLen);
/* Flush the decryption sink and wait for the decryption to get
ready. */
if ((rc = write_buffer (decsink, NULL, 0)))
goto failure;
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
decsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Check the size of the decrypted data */
tmpstream->Stat (&tmpStat, 0);
if (tmpStat.cbSize.QuadPart > UINT_MAX)
{
log_error ("%s:%s: No one should write so large mails.",
SRCNAME, __func__);
goto failure;
}
/* Copy the decrypted stream to the message editor. */
{
LARGE_INTEGER off;
ULONG nread;
char buffer[(unsigned int)tmpStat.cbSize.QuadPart + 1];
memset (buffer, 0, sizeof buffer);
off.QuadPart = 0;
hr = tmpstream->Seek (off, STREAM_SEEK_SET, NULL);
if (hr)
{
log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
hr = tmpstream->Read (buffer, sizeof (buffer) - 1, &nread);
if (hr)
{
log_error ("%s:%s: IStream::Read failed: hr=%#lx",
SRCNAME, __func__, hr);
rc = gpg_error (GPG_ERR_EIO);
goto failure;
}
if (strlen (buffer) > 1)
{
/* Now replace the crypto data with the decrypted data or show it
somehow.*/
int err = 0;
if (flags & DATA_SELECTION)
{
err = put_oom_string (selection, "Text", buffer);
}
else if (flags & DATA_BODY)
{
err = put_oom_string (mailItem, "Body", buffer);
}
if (err)
{
MessageBox (NULL, buffer,
flags & OP_DECRYPT ? _("Plain text") :
_("Signed text"),
MB_ICONINFORMATION|MB_OK);
}
}
else
{
/* Just to be save not to overwrite the selection with
an empty buffer */
log_error ("%s:%s: unexpected problem ", SRCNAME, __func__);
goto failure;
}
}
failure:
if (rc)
log_debug ("%s:%s: failed rc=%d (%s) <%s>", SRCNAME, __func__, rc,
gpg_strerror (rc), gpg_strsource (rc));
engine_cancel (filter);
RELDISP (mailItem);
RELDISP (selection);
RELDISP (wordEditor);
RELDISP (wordApplication);
xfree (encData);
xfree (senderAddr);
xfree (subject);
if (tmpstream)
tmpstream->Release();
return S_OK;
}
/* 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;
/* 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, FALSE, &pStream) == S_OK)
{
pbitmap = Gdiplus::Bitmap::FromStream (pStream);
pStream->Release();
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;
}
/* Adds an encrypted attachment if the flag OP_SIGN is set
a detached signature of the encrypted file is also added. */
static HRESULT
attachEncryptedFile (LPDISPATCH ctrl, int flags)
{
LPDISPATCH context = NULL;
LPDISPATCH mailItem = NULL;
LPDISPATCH sender = NULL;
LPDISPATCH recipients = NULL;
HRESULT hr;
char* senderAddr = NULL;
char** recipientAddrs = NULL;
char* subject = NULL;
HWND curWindow;
char *fileToEncrypt = NULL;
wchar_t *fileToEncryptW = NULL;
wchar_t *encryptedFile = NULL;
wchar_t *attachName = NULL;
HANDLE hFile = NULL;
HANDLE hEncFile = NULL;
unsigned int session_number;
struct sink_s encsinkmem;
sink_t encsink = &encsinkmem;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
engine_filter_t filter = NULL;
protocol_t protocol;
int rc = 0;
int i = 0;
memset (encsink, 0, sizeof *encsink);
memset (sink, 0, sizeof *sink);
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
/* First do the check for recipients as this is likely
to fail */
mailItem = get_oom_object (context, "CurrentItem");
sender = get_oom_object (mailItem, "Session.CurrentUser");
recipients = get_oom_object (mailItem, "Recipients");
recipientAddrs = get_oom_recipients (recipients);
if (!recipientAddrs || !(*recipientAddrs))
{
MessageBox (NULL,
_("Please add at least one recipent."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
goto failure;
}
/* Get a file handle to read from */
fileToEncrypt = get_open_filename (NULL, _("Select file to encrypt"));
if (!fileToEncrypt)
{
log_debug ("No file selected");
goto failure;
}
fileToEncryptW = utf8_to_wchar2 (fileToEncrypt, strlen(fileToEncrypt));
xfree (fileToEncrypt);
hFile = CreateFileW (fileToEncryptW,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
/* Should not happen as the Open File dialog
should have prevented this.
Maybe this also happens when a file is
not readable. In that case we might want
to switch to a localized error naming the file. */
MessageBox (NULL,
"Internal error in GpgOL.\n"
"Could not open File.",
_("GpgOL"),
MB_ICONERROR|MB_OK);
return S_OK;
}
/* Now do the encryption preperations */
if (!mailItem || !sender || !recipients)
{
MessageBox (NULL,
"Internal error in GpgOL.\n"
"Could not find all objects.",
_("GpgOL"),
MB_ICONERROR|MB_OK);
log_error ("%s:%s: Could not find all objects.",
SRCNAME, __func__);
goto failure;
}
senderAddr = get_pa_string (sender, PR_SMTP_ADDRESS);
curWindow = get_oom_context_window (context);
session_number = engine_new_session_number ();
subject = get_oom_string (mailItem, "Subject");
/* Prepare the encryption sink */
if ((rc = engine_create_filter (&filter, write_buffer_for_cb, sink)))
{
goto failure;
}
encsink->cb_data = filter;
encsink->writefnc = sink_encryption_write;
engine_set_session_number (filter, session_number);
engine_set_session_title (filter, subject ? subject :_("GpgOL"));
if ((rc=engine_encrypt_prepare (filter, curWindow,
PROTOCOL_UNKNOWN,
ENGINE_FLAG_BINARY_OUTPUT,
senderAddr, recipientAddrs, &protocol)))
{
log_error ("%s:%s: engine encrypt prepare failed : %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
attachName = get_pretty_attachment_name (fileToEncryptW, protocol, 0);
if (!attachName)
{
log_error ("%s:%s: Could not get a decent attachment name",
SRCNAME, __func__);
goto failure;
}
encryptedFile = get_tmp_outfile (attachName, &hEncFile);
sink->cb_data = hEncFile;
sink->writefnc = sink_file_write;
if ((rc=engine_encrypt_start (filter, 0)))
{
log_error ("%s:%s: engine encrypt start failed: %s",
SRCNAME, __func__, gpg_strerror (rc));
goto failure;
}
if ((rc=copyFileToSink (hFile, encsink)))
goto failure;
/* Lets hope the user did not select a huge file. We are hanging
here until encryption is completed.. */
if ((rc = engine_wait (filter)))
goto failure;
filter = NULL; /* Not valid anymore. */
encsink->cb_data = NULL; /* Not needed anymore. */
if (!sink->enc_counter)
{
log_error ("%s:%s: nothing received from engine", SRCNAME, __func__);
goto failure;
}
/* Now we have an encrypted file behind encryptedFile. Let's add it */
add_oom_attachment (mailItem, encryptedFile);
if (flags & OP_SIGN)
{
attachSignature (mailItem, subject, hEncFile, protocol, session_number,
curWindow, encryptedFile, senderAddr);
}
failure:
if (filter)
engine_cancel (filter);
if (hEncFile)
{
CloseHandle (hEncFile);
DeleteFileW (encryptedFile);
}
xfree (senderAddr);
xfree (encryptedFile);
xfree (fileToEncryptW);
xfree (attachName);
xfree (subject);
RELDISP (mailItem);
RELDISP (sender);
RELDISP (recipients);
if (hFile)
CloseHandle (hFile);
if (recipientAddrs)
{
for (i=0; recipientAddrs && recipientAddrs[i]; i++)
xfree (recipientAddrs[i]);
xfree (recipientAddrs);
}
return S_OK;
}
HRESULT
startCertManager (LPDISPATCH ctrl)
{
HRESULT hr;
LPDISPATCH context;
HWND curWindow;
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
curWindow = get_oom_context_window (context);
engine_start_keymanager (curWindow);
return S_OK;
}
HRESULT
decryptBody (LPDISPATCH ctrl)
{
return do_reader_action (ctrl, OP_DECRYPT | DATA_BODY);
}
HRESULT
decryptSelection (LPDISPATCH ctrl)
{
return do_reader_action (ctrl, OP_DECRYPT | DATA_SELECTION);
}
HRESULT
encryptBody (LPDISPATCH ctrl)
{
return do_composer_action (ctrl, OP_ENCRYPT | DATA_BODY);
}
HRESULT
encryptSelection (LPDISPATCH ctrl)
{
return do_composer_action (ctrl, OP_ENCRYPT | DATA_SELECTION);
}
HRESULT
addEncSignedAttachment (LPDISPATCH ctrl)
{
return attachEncryptedFile (ctrl, OP_SIGN);
}
HRESULT
addEncAttachment (LPDISPATCH ctrl)
{
return attachEncryptedFile (ctrl, 0);
}
HRESULT signBody (LPDISPATCH ctrl)
{
return do_composer_action (ctrl, DATA_BODY | OP_SIGN);
}
HRESULT verifyBody (LPDISPATCH ctrl)
{
return do_reader_action (ctrl, DATA_BODY | OP_VERIFY);
}
static void
message_flag_status (HWND window, int flags)
{
const char * message;
if (flags & OP_ENCRYPT && flags & OP_SIGN)
{
message = _("The message will be signed & encrypted.");
}
else if (flags & OP_ENCRYPT)
{
message = _("The message will be encrypted.");
}
else if (flags & OP_SIGN)
{
message = _("The message will be signed.");
}
else
{
message = _("The message will be sent plain and without a signature.");
}
MessageBox (NULL,
message,
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
static HRESULT
mark_mime_action (LPDISPATCH ctrl, int flags)
{
HRESULT hr;
HRESULT rc = E_FAIL;
HWND cur_window;
LPDISPATCH context = NULL,
mailitem = NULL;
LPMESSAGE message = NULL;
int oldflags,
newflags;
log_debug ("%s:%s: enter", SRCNAME, __func__);
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
cur_window = get_oom_context_window (context);
mailitem = get_oom_object (context, "CurrentItem");
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
goto done;
}
message = get_oom_message (mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
goto done;
}
oldflags = get_gpgol_draft_info_flags (message);
newflags = oldflags xor flags;
if (set_gpgol_draft_info_flags (message, newflags))
{
log_error ("%s:%s: Failed to set draft flags.",
SRCNAME, __func__);
}
message_flag_status (cur_window, newflags);
rc = S_OK;
done:
RELDISP (context);
RELDISP (mailitem);
RELDISP (message);
return rc;
}
HRESULT mime_sign (LPDISPATCH ctrl)
{
return mark_mime_action (ctrl, OP_SIGN);
}
HRESULT mime_encrypt (LPDISPATCH ctrl)
{
return mark_mime_action (ctrl, OP_ENCRYPT);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 28, 11:08 PM (5 h, 23 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
3d/c5/f2d79f37800f4fe56a2cb2d1aa92

Event Timeline