diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp index 1674567..4c02848 100644 --- a/src/mapihelp.cpp +++ b/src/mapihelp.cpp @@ -1,3978 +1,3978 @@ /* mapihelp.cpp - Helper functions for MAPI * Copyright (C) 2005, 2007, 2008 g10 Code GmbH - * + * * This file is part of GpgOL. - * + * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "mymapi.h" #include "mymapitags.h" #include "common.h" #include "rfc822parse.h" #include "mapihelp.h" #include "parsetlv.h" #include "oomhelp.h" #include #ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA #define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011 #endif #ifndef CRYPT_E_ASN1_BADTAG #define CRYPT_E_ASN1_BADTAG 0x8009310B #endif static int get_attach_method (LPATTACH obj); static int has_smime_filename (LPATTACH obj); static char *get_attach_mime_tag (LPATTACH obj); /* Print a MAPI property to the log stream. */ void log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname) { TSTART; HRESULT hr; LPSPropValue propval = NULL; size_t keylen; void *key; char *buf; if (!message) { TRETURN; /* 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); TRETURN; } - + 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) ); TRETURN; } MAPIFreeBuffer (propval); TRETURN; } /* Helper to create a named property. */ -static ULONG +static ULONG create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func) { TSTART; HRESULT hr; LPSPropTagArray proparr = NULL; MAPINAMEID mnid, *pmnid; wchar_t *propname = xwcsdup (name); /* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties. */ GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06, 0x1b, 0x03, 0x10, 0x04}}; ULONG result; - + memset (&mnid, 0, sizeof mnid); mnid.lpguid = &guid; mnid.ulKind = MNID_STRING; mnid.Kind.lpwstrName = propname; pmnid = &mnid; hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr); xfree (propname); if (FAILED (hr)) proparr = NULL; - if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) + if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) { log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n", - SRCNAME, func, hr); + SRCNAME, func, hr); result = 0; } else result = (proparr->aulPropTag[0] & 0xFFFF0000); if (proparr) MAPIFreeBuffer (proparr); - + TRETURN result; } /* Return the property tag for GpgOL Msg Class. */ -int +int get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__))) { TRETURN -1; } *r_tag |= PT_STRING8; TRETURN 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 +int get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Old Msg Class", __func__))) { TRETURN -1; } *r_tag |= PT_STRING8; TRETURN 0; } /* Return the property tag for GpgOL Attach Type. */ -int +int get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__))) { TRETURN -1; } *r_tag |= PT_LONG; TRETURN 0; } /* Return the property tag for GpgOL Protect IV. */ -int +int get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__))) { TRETURN -1; } *r_tag |= PT_BINARY; TRETURN 0; } /* Return the property tag for GpgOL Last Decrypted. */ -int +int get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__))) { TRETURN -1; } *r_tag |= PT_BINARY; TRETURN 0; } /* Return the property tag for GpgOL MIME structure. */ -int +int get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__))) { TRETURN -1; } *r_tag |= PT_STRING8; TRETURN 0; } /* Return the property tag for GpgOL Charset. */ -int +int get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__))) { TRETURN -1; } *r_tag |= PT_STRING8; TRETURN 0; } /* Return the property tag for GpgOL Draft Info. */ -int +int get_gpgoldraftinfo_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Draft Info", __func__))) { TRETURN -1; } *r_tag |= PT_STRING8; TRETURN 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) { TSTART; HRESULT hr; LPSPropTagArray proparr = NULL; - MAPINAMEID mnid, *pmnid; + MAPINAMEID mnid, *pmnid; /* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */ GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0, 0x4F, 0xD6, 0x56, 0x85}}; wchar_t propname[] = L"Internet Charset Body"; int result; memset (&mnid, 0, sizeof mnid); mnid.lpguid = &guid; mnid.ulKind = MNID_STRING; mnid.Kind.lpwstrName = propname; pmnid = &mnid; hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr); if (FAILED (hr)) proparr = NULL; - if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) + if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) { log_debug ("%s:%s: can't get the Internet Charset Body property:" - " hr=%#lx\n", SRCNAME, __func__, hr); + " hr=%#lx\n", SRCNAME, __func__, hr); result = -1; } else { result = 0; *r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY); } if (proparr) MAPIFreeBuffer (proparr); - + TRETURN result; } /* Return the property tag for GpgOL UUID Info. */ static int get_gpgoluid_tag (LPMESSAGE message, ULONG *r_tag) { TSTART; if (!(*r_tag = create_gpgol_tag (message, L"GpgOL UID", __func__))) { TRETURN -1; } *r_tag |= PT_UNICODE; TRETURN 0; } char * mapi_get_uid (LPMESSAGE msg) { TSTART; /* If the UUID is not in OOM maybe we find it in mapi. */ if (!msg) { log_error ("%s:%s: Called without message", SRCNAME, __func__); TRETURN NULL; } ULONG tag; if (get_gpgoluid_tag (msg, &tag)) { log_debug ("%s:%s: Failed to get tag for '%p'", SRCNAME, __func__, msg); TRETURN NULL; } LPSPropValue propval = NULL; HRESULT hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (hr) { log_debug ("%s:%s: Failed to get prop for '%p'", SRCNAME, __func__, msg); TRETURN NULL; } char *ret = NULL; if (PROP_TYPE (propval->ulPropTag) == PT_UNICODE) { ret = wchar_to_utf8 (propval->Value.lpszW); log_debug ("%s:%s: Fund uuid in MAPI for %p", SRCNAME, __func__, msg); } else if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) { ret = xstrdup (propval->Value.lpszA); log_debug ("%s:%s: Fund uuid in MAPI for %p", SRCNAME, __func__, msg); } MAPIFreeBuffer (propval); TRETURN ret; } /* A Wrapper around the SaveChanges method. This function should be called indirect through the mapi_save_changes macro. Returns 0 on success. */ int mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body, const char *dbg_file, const char *dbg_func) { TSTART; HRESULT hr; SPropTagArray proparray; int any = 0; - + if (mapi_has_last_decrypted (message)) { proparray.cValues = 1; proparray.aulPropTag[0] = PR_BODY; hr = message->DeleteProps (&proparray, NULL); if (hr) log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed", log_srcname (dbg_file), dbg_func); else any = 1; proparray.cValues = 1; proparray.aulPropTag[0] = PR_BODY_HTML; hr = message->DeleteProps (&proparray, NULL); if (hr) log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed", log_srcname (dbg_file), dbg_func); else any = 1; } if (!only_del_body || any) { int i; for (i = 0, hr = 0; hr && i < 10; i++) { hr = message->SaveChanges (flags); if (hr) { log_debug ("%s:%s: Failed try to save.", SRCNAME, __func__); Sleep (1000); } } if (hr) { log_error ("%s:%s: SaveChanges(%lu) failed: hr=%#lx\n", log_srcname (dbg_file), dbg_func, - (unsigned long)flags, hr); + (unsigned long)flags, hr); TRETURN -1; } } - + TRETURN 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; + 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) { TRETURN -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)) + if (FAILED (hr)) { pProps = NULL; log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n", - SRCNAME, __func__, name, hr); + SRCNAME, __func__, name, hr); result = -1; } else { pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8; pv.Value.lpszA = (char *)val; - hr = HrSetOneProp(msg, &pv); + hr = HrSetOneProp(msg, &pv); if (hr) { log_error ("%s:%s: can't set header `%s': hr=%#lx\n", - SRCNAME, __func__, name, hr); + SRCNAME, __func__, name, hr); result = -1; } else result = 0; } if (pProps) MAPIFreeBuffer (pProps); TRETURN result; } /* Return the data of the first attachment as string. Returns empty string on failure. */ static std::string mapi_get_first_attach_data (LPMESSAGE message) { TSTART; HRESULT hr; LPSTREAM stream = nullptr; ULONG bRead; std::string ret; if (!message) { STRANGEPOINT; TRETURN ret; } mapi_attach_item_t *table = mapi_create_attach_table (message, 0); if (!table) { log_debug ("%s:%s: Message has no attachments", SRCNAME, __func__); TRETURN ret; } if (table->end_of_table) { log_debug ("%s:%s: Message has no attachments", SRCNAME, __func__); mapi_release_attach_table (table); TRETURN ret; } stream = mapi_get_attach_as_stream (message, table, nullptr); mapi_release_attach_table (table); if (!stream) { log_debug ("%s:%s: Failed to get attachment as stream", SRCNAME, __func__); TRETURN ret; } char buf[8192]; while ((hr = stream->Read (buf, 8192, &bRead)) == S_OK || hr == S_FALSE) { if (!bRead) { // EOF break; } ret += std::string (buf, bRead); } gpgol_release (stream); TRETURN ret; } /* Return the headers as ASCII string. Returns empty string on failure. */ std::string mapi_get_header (LPMESSAGE message) { TSTART; HRESULT hr; LPSTREAM stream = nullptr; ULONG bRead; std::string ret; if (!message) { TRETURN ret; } hr = gpgol_openProperty (message, PR_TRANSPORT_MESSAGE_HEADERS_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN ret; } char buf[8192]; while ((hr = stream->Read (buf, 8192, &bRead)) == S_OK || hr == S_FALSE) { if (!bRead) { // EOF break; } ret += std::string (buf, bRead); } gpgol_release (stream); TRETURN ret; } /* Return the body as a new IStream object. Returns NULL on failure. The stream Returns the body as an ASCII stream (Use mapi_get_body for an UTF-8 value). */ LPSTREAM mapi_get_body_as_stream (LPMESSAGE message) { TSTART; HRESULT hr; ULONG tag; LPSTREAM stream; if (!message) { TRETURN 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 = gpgol_openProperty (message, tag, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (!hr) { TRETURN 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 = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } TRETURN 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) { TSTART; 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 = gpgol_openProperty (message, PR_BODY, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } - + hr = stream->Stat (&statInfo, STATFLAG_NONAME); if (hr) { log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); TRETURN NULL; } - + /* Fixme: We might want to read only the first 1k to decide whether this is actually an OpenPGP message and only then continue reading. */ body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2); hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread); if (hr) { log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (body); gpgol_release (stream); TRETURN NULL; } body[nread] = 0; body[nread+1] = 0; if (nread != statInfo.cbSize.QuadPart) { log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (body); gpgol_release (stream); TRETURN NULL; } gpgol_release (stream); - + { char *tmp; tmp = wchar_to_utf8 ((wchar_t*)body); if (!tmp) log_debug ("%s: error converting to utf8\n", __func__); else { xfree (body); body = tmp; } } } if (r_nbytes) *r_nbytes = strlen (body); TRETURN body; } /* Look at the body of the MESSAGE and try to figure out whether this is a supported PGP message. Returns the new message class or NULL if it does not look like a PGP message. If r_nobody is not null it is set to true if no body was found. */ static char * get_msgcls_from_pgp_lines (LPMESSAGE message, bool *r_nobody = nullptr) { TSTART; HRESULT hr; LPSTREAM stream; STATSTG statInfo; ULONG nread; size_t nbytes; char *body = NULL; char *p; char *msgcls = NULL; int is_wchar = 0; if (r_nobody) { *r_nobody = false; } stream = mapi_get_body_as_stream (message); if (!stream) { log_debug ("%s:%s: Failed to get body ASCII stream.", SRCNAME, __func__); hr = gpgol_openProperty (message, PR_BODY_W, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); if (hr) { log_error ("%s:%s: Failed to get w_body stream. : hr=%#lx", SRCNAME, __func__, hr); if (r_nobody) { *r_nobody = true; } TRETURN NULL; } else { is_wchar = 1; } } hr = stream->Stat (&statInfo, STATFLAG_NONAME); if (hr) { log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); TRETURN NULL; } /* We read only the first 1k to decide whether this is actually an OpenPGP armored message . */ nbytes = (size_t)statInfo.cbSize.QuadPart; if (nbytes > 1024*2) nbytes = 1024*2; body = (char*)xmalloc (nbytes + 2); hr = stream->Read (body, nbytes, &nread); if (hr) { log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (body); gpgol_release (stream); TRETURN NULL; } body[nread] = 0; body[nread+1] = 0; if (nread != nbytes) { log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (body); gpgol_release (stream); TRETURN NULL; } gpgol_release (stream); if (is_wchar) { char *tmp; tmp = wchar_to_utf8 ((wchar_t*)body); if (!tmp) log_debug ("%s: error converting to utf8\n", __func__); else { xfree (body); body = tmp; } } /* The first ~1k of the body of the message is now available in the utf-8 string BODY. Walk over it to figure out its type. */ for (p=body; p && *p; p = ((p=strchr (p+1, '\n')) ? (p+1) : NULL)) { if (!strncmp (p, "-----BEGIN PGP ", 15)) { /* Enabling clearsigned detection for Outlook 2010 and later would result in data loss as the signature is not reverted. */ if (!strncmp (p+15, "SIGNED MESSAGE-----", 19) && trailing_ws_p (p+15+19)) msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned"); else if (!strncmp (p+15, "MESSAGE-----", 12) && trailing_ws_p (p+15+12)) msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage"); break; } else if (!trailing_ws_p (p)) { /* We have text before the message. In that case we need to break because some bad MUA's like Outlook do not insert quote characters before a replied to message. In that case the reply to an inline Mail from an Outlook without GpgOL enabled could cause the behavior that we would detect the original message. */ log_debug ("%s:%s: Detected non whitespace %c before a PGP Marker", SRCNAME, __func__, *p); break; } } xfree (body); TRETURN msgcls; } -/* Check whether the message is really a CMS encrypted message. +/* 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: + 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); TRETURN -1; } memdbg_addRef (mapitable); hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); TRETURN -1; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (n_attach != 1) { FreeProws (mapirows); gpgol_release (mapitable); log_debug ("%s:%s: not just one attachment", SRCNAME, __func__); TRETURN -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, + SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); goto leave; } memdbg_addRef (att); 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 = gpgol_openProperty (att, 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; } memdbg_addRef (stream); hr = stream->Read (buffer, sizeof buffer, &nread); if ( hr != S_OK ) { log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } if (nread < sizeof buffer) { log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); goto leave; } p = buffer; n = nread; if (parse_tlv (&p, &n, &ti)) goto leave; if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE && ti.is_cons) ) goto leave; if (parse_tlv (&p, &n, &ti)) goto leave; if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID && !ti.is_cons && ti.length) || ti.length > n) goto leave; /* Now is this enveloped data (1.2.840.113549.1.7.3) or signed data (1.2.840.113549.1.7.2) ? */ if (ti.length == 9) { if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9)) result = 1; /* Encrypted. */ else if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9)) result = 0; /* Signed. */ } - + leave: if (stream) gpgol_release (stream); if (att) gpgol_release (att); FreeProws (mapirows); gpgol_release (mapitable); TRETURN 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); TRETURN NULL; } memdbg_addRef (mapitable); hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); TRETURN NULL; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; if (n_attach < 1) { FreeProws (mapirows); gpgol_release (mapitable); log_debug ("%s:%s: less then one attachment", SRCNAME, __func__); TRETURN 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); + NULL, MAPI_BEST_ACCESS, &att); if (FAILED (hr)) { log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx", - SRCNAME, __func__, pos, + SRCNAME, __func__, pos, mapirows->aRow[pos].lpProps[0].Value.l, hr); goto leave; } memdbg_addRef (att); /* Note: We do not expect a filename. */ if (get_attach_method (att) != ATTACH_BY_VALUE) { log_debug ("%s:%s: wrong attach method", SRCNAME, __func__); goto leave; } result = get_attach_mime_tag (att); - + leave: if (att) gpgol_release (att); FreeProws (mapirows); gpgol_release (mapitable); TRETURN result; } /* Look at the first attachment's content type to determine the messageclass. */ static char * get_msgcls_from_first_attachment (LPMESSAGE message) { TSTART; char *ret = nullptr; char *attach_mime = get_first_attach_mime_tag (message); if (!attach_mime) { TRETURN nullptr; } if (!strcmp (attach_mime, "application/pgp-encrypted")) { ret = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted"); xfree (attach_mime); } else if (!strcmp (attach_mime, "application/pgp-signature")) { ret = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (attach_mime); } TRETURN ret; } /* Helper for mapi_change_message_class. Returns the new message class as an allocated string. Most message today are of the message class "IPM.Note". However a PGP/MIME encrypted message also has this class. We need to see whether we can detect such a mail right here and change the message class accordingly. */ static char * change_message_class_ipm_note (LPMESSAGE message) { TSTART; char *newvalue = NULL; char *ct, *proto; ct = mapi_get_message_content_type (message, &proto, NULL); log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct ? ct : "null"); /* First the simplest check */ if (ct && !strcmp (ct, "wks.confirmation.mail")) { log_dbg ("Setting WKSConfirmation"); newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation"); } else if (ct && proto) { log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto); if (!strcmp (ct, "multipart/encrypted") && !strcmp (proto, "application/pgp-encrypted")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted"); } else if (!strcmp (ct, "multipart/signed") && !strcmp (proto, "application/pgp-signature")) { /* Sometimes we receive a PGP/MIME signed message with a class IPM.Note. */ newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } else if (!strcmp (ct, "multipart/mixed") && !strcmp (proto, "application/pgp-encrypted")) { /* This case happens if an encrypted mail is moved by outlook local filter rules. */ newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted"); } xfree (proto); } else if (ct && !strcmp (ct, "application/ms-tnef")) { /* ms-tnef can either be inline PGP or PGP/MIME. First check for inline and then look at the attachments if they look like PGP /MIME .*/ newvalue = get_msgcls_from_pgp_lines (message); if (!newvalue) { /* So no PGP Inline. Lets look at the attachment. */ newvalue = get_msgcls_from_first_attachment (message); } } else if (!ct || !strcmp (ct, "text/plain") || !strcmp (ct, "multipart/mixed") || !strcmp (ct, "multipart/alternative") || !strcmp (ct, "multipart/related") || !strcmp (ct, "text/html")) { bool has_no_body = false; /* It is quite common to have a multipart/mixed or alternative mail with separate encrypted PGP parts. Look at the body to decide. */ newvalue = get_msgcls_from_pgp_lines (message, &has_no_body); if (!newvalue && has_no_body && ct && !strcmp (ct, "multipart/mixed")) { /* This is uncommon. But some Exchanges might break a PGP/MIME mail this way. Let's take a look at the attachments. Maybe it's a PGP/MIME mail. */ log_debug ("%s:%s: Multipart mixed without body found. Looking at attachments.", SRCNAME, __func__); newvalue = get_msgcls_from_first_attachment (message); } } xfree (ct); TRETURN 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) { TSTART; 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 if (proto && !strcmp (ct, "multipart/signed") && (!strcmp (proto, "application/pkcs7-signature") || !strcmp (proto, "application/x-pkcs7-signature"))) { log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto); newvalue = xstrdup ("IPM.Note.GpgOL.SM.MultipartSigned"); } 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) { log_debug ("%s:%s WARNING: Failed to detect smime. Fallback " "to OpaqueEncrypted", SRCNAME, __func__); newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); } TRETURN 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) { TSTART; 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 + if (proto && !strcmp (ct, "multipart/signed") && !strcmp (proto, "application/pgp-signature")) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } else if (!strcmp (ct, "wks.confirmation.mail")) { newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation"); } xfree (proto); xfree (ct); } else log_debug ("%s:%s: message has no content type", SRCNAME, __func__); - + TRETURN 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) { TSTART; 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.SM.MultipartSigned"); } else if (!strcmp (ct, "multipart/signed") && (!strcmp (proto, "application/pgp-signature"))) { newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); } } - + if (!newvalue && (!strcmp (ct, "text/plain") || !strcmp (ct, "multipart/alternative") || !strcmp (ct, "multipart/mixed"))) { newvalue = get_msgcls_from_pgp_lines (message); } - + if (!newvalue) { switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; } } - + xfree (smtype); xfree (proto); xfree (ct); } else { log_debug ("%s:%s: message has no content type", SRCNAME, __func__); if (is_cexenc) { switch (is_really_cms_encrypted (message)) { case 0: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned"); break; case 1: newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); break; } } else { char *mimetag; mimetag = get_first_attach_mime_tag (message); if (mimetag && !strcmp (mimetag, "multipart/signed")) newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned"); xfree (mimetag); } if (!newvalue) { newvalue = get_msgcls_from_pgp_lines (message); } } if (!newvalue) newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted"); TRETURN newvalue; } static msgtype_t string_to_type (const char *s) { TSTART; if (!s || strlen (s) < 14) { TRETURN MSGTYPE_UNKNOWN; } if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.')) { s += 14; if (!*s) { TRETURN MSGTYPE_GPGOL; } else if (!strcmp (s, ".SM.MultipartSigned")) { TRETURN MSGTYPE_GPGOL_MULTIPART_SIGNED; } else if (!strcmp (s, ".MultipartSigned")) { TRETURN MSGTYPE_GPGOL_MULTIPART_SIGNED; } else if (!strcmp (s, ".MultipartEncrypted")) { TRETURN MSGTYPE_GPGOL_MULTIPART_ENCRYPTED; } else if (!strcmp (s, ".OpaqueSigned")) { TRETURN MSGTYPE_GPGOL_OPAQUE_SIGNED; } else if (!strcmp (s, ".OpaqueEncrypted")) { TRETURN MSGTYPE_GPGOL_OPAQUE_ENCRYPTED; } else if (!strcmp (s, ".ClearSigned")) { TRETURN MSGTYPE_GPGOL_CLEAR_SIGNED; } else if (!strcmp (s, ".PGPMessage")) { TRETURN MSGTYPE_GPGOL_PGP_MESSAGE; } else if (!strcmp (s, ".WKSConfirmation")) { TRETURN MSGTYPE_GPGOL_WKS_CONFIRMATION; } else log_debug ("%s:%s: message class `%s' not supported", SRCNAME, __func__, s-14); } else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.')) { TRETURN MSGTYPE_SMIME; } TRETURN MSGTYPE_UNKNOWN; } /* This function checks whether MESSAGE requires processing by us and adjusts the message class to our own. By passing true for SYNC_OVERRIDE the actual MAPI message class will be updated to our own message class overide. TRETURN true if the message was changed. */ int mapi_change_message_class (LPMESSAGE message, int sync_override, msgtype_t *r_type) { TSTART; HRESULT hr; ULONG tag; SPropValue prop; LPSPropValue propval = NULL; char *newvalue = NULL; int need_save = 0; int have_override = 0; if (!message) { TRETURN 0; /* No message: Nop. */ } if (get_gpgolmsgclass_tag (message, &tag) ) { TRETURN 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); TRETURN 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'", + + log_debug ("%s:%s: checking message class `%s'", SRCNAME, __func__, s); if (!strcmp (s, "IPM.Note")) { newvalue = change_message_class_ipm_note (message); } else if (opt.enable_smime && (!strcmp (s, "IPM.Note.SMIME") || !strcmp (s, "IPM.Note.GpgOL"))) { newvalue = change_message_class_ipm_note_smime (message); } else if (opt.enable_smime && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.')) { /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional but the previous condition has already taken care of this). Note that we can't just insert a new part and keep the SMIME; we need to change the SMIME part of the class name so that Outlook does not process it as an SMIME message. */ char *tmp = change_message_class_ipm_note_smime_multipartsigned (message); /* This case happens even for PGP/MIME mails but that is ok as we later fiddle out the protocol. But we have to check if this is a WKS Mail now so that we can do the special handling for that. */ if (tmp && !strcmp (tmp, "IPM.Note.GpgOL.WKSConfirmation")) { newvalue = tmp; } else { xfree (tmp); gpgrt_asprintf (&newvalue, "IPM.Note.GpgOL.SM%s", s+14); memdbg_alloc (newvalue); } } else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned")) { /* This is an S/MIME message class but smime support is not enabled. We need to check whether this is actually a PGP/MIME message. */ newvalue = change_message_class_ipm_note_smime_multipartsigned (message); } else if (sync_override && have_override && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.')) { /* In case the original message class is not yet an GpgOL class we set it here. This is needed to convince Outlook not to do any special processing for IPM.Note.SMIME etc. */ LPSPropValue propval2 = NULL; hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval2); if (!SUCCEEDED (hr)) { log_debug ("%s:%s: Failed to get PR_MESSAGE_CLASS_A property.", SRCNAME, __func__); } else if (PROP_TYPE (propval2->ulPropTag) != PT_STRING8) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is not string.", SRCNAME, __func__); } else if (!propval2->Value.lpszA) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is null.", SRCNAME, __func__); } else if (!strcmp (propval2->Value.lpszA, s)) { log_debug ("%s:%s: PR_MESSAGE_CLASS_A is already the same.", SRCNAME, __func__); } else { newvalue = (char*)xstrdup (s); } MAPIFreeBuffer (propval2); } - else if (opt.enable_smime + else if (opt.enable_smime && (!strcmp (s, "IPM.Note.Secure.CexSig") || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc")))) { newvalue = change_message_class_ipm_note_secure_cex (message, cexenc); } if (r_type && !newvalue) { *r_type = string_to_type (s); } } if (!newvalue) { log_debug ("%s:%s Message is not a crypto message or already in the right class.", SRCNAME, __func__); } else { if (r_type) { *r_type = string_to_type (newvalue); } /* Save old message class if not yet done. (The second condition is just a failsafe check). */ if (!get_gpgololdmsgclass_tag (message, &tag) && PROP_TYPE (propval->ulPropTag) == PT_STRING8) { LPSPropValue propval2 = NULL; hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2); if (!FAILED (hr)) MAPIFreeBuffer (propval2); else { /* No such property - save it. */ log_debug ("%s:%s: saving old message class\n", SRCNAME, __func__); prop.ulPropTag = tag; - prop.Value.lpszA = propval->Value.lpszA; + 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); TRETURN 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; + 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); TRETURN 0; } need_save = 1; } MAPIFreeBuffer (propval); if (need_save) { if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE)) { TRETURN 0; } } TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; char *retstr; if (!message) { TRETURN 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); TRETURN 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); TRETURN 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) { TSTART; HRESULT hr; ULONG tag; LPSPropValue propval = NULL; char *retstr; if (!message) { TRETURN NULL; } - + if (get_gpgololdmsgclass_tag (message, &tag)) { TRETURN NULL; } hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n", SRCNAME, __func__, hr); TRETURN NULL; } if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) retstr = xstrdup (propval->Value.lpszA); else retstr = NULL; - + MAPIFreeBuffer (propval); TRETURN 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. */ + internationalized domain names. */ char * mapi_get_sender (LPMESSAGE message) { TSTART; HRESULT hr; LPSPropValue propval = NULL; char *buf; char *p0, *p; - + if (!message) { TRETURN 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); TRETURN NULL; } - - if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) + + 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); TRETURN NULL; } - + buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); if (!buf) { log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__); TRETURN 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__, anonstr (buf)); xfree (buf); TRETURN 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__, anonstr (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__, anonstr (buf)); TRETURN buf; } static char * resolve_ex_from_address (LPMESSAGE message) { TSTART; HRESULT hr; char *sender_entryid; size_t entryidlen; LPMAPISESSION session; ULONG utype; LPUNKNOWN user; LPSPropValue propval = NULL; char *buf; if (g_ol_version_major < 14) { log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__); TRETURN NULL; } sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID, &entryidlen); if (!sender_entryid) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); TRETURN NULL; } session = get_oom_mapi_session (); if (!session) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); xfree (sender_entryid); TRETURN NULL; } hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid, &IID_IMailUser, MAPI_BEST_ACCESS | MAPI_CACHE_ONLY, &utype, (IUnknown**)&user); if (FAILED (hr)) { log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.", SRCNAME, __func__); hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid, &IID_IMailUser, MAPI_BEST_ACCESS, &utype, (IUnknown**)&user); } gpgol_release (session); if (FAILED (hr)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); TRETURN NULL; } hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval); if (FAILED (hr)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); TRETURN 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); TRETURN NULL; } buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; char *buf; ULONG try_props[3] = {PidTagSenderSmtpAddress_W, PR_SENT_REPRESENTING_SMTP_ADDRESS_W, PR_SENDER_EMAIL_ADDRESS_W}; if (!message) { TRETURN xstrdup ("[no message]"); /* Ooops. */ } for (int i = 0; i < 3; i++) { /* We try to get different properties first as they contain the SMTP address of the sender. EMAIL address can be some LDAP stuff for exchange. */ hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i], &propval); if (!FAILED (hr)) { break; } } /* This is the last result that should always work but not necessarily contain an SMTP Address. */ if (FAILED (hr)) { log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n", SRCNAME, __func__, hr); TRETURN NULL; } - if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) + 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); TRETURN NULL; } - + buf = wchar_to_utf8 (propval->Value.lpszW); MAPIFreeBuffer (propval); if (!buf) { log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__); TRETURN NULL; } if (strstr (buf, "/o=")) { char *buf2; /* If both SMTP Address properties are not set we need to fallback to resolve the address through the address book */ log_debug ("%s:%s: resolving exchange address.", SRCNAME, __func__); buf2 = resolve_ex_from_address (message); if (buf2) { xfree (buf); TRETURN buf2; } } TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; char *buf; - + if (!message) { TRETURN 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); TRETURN xstrdup (_("[no subject]")); } - - if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) + + 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); TRETURN 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__); TRETURN xstrdup (_("[no subject]")); } TRETURN 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) { TSTART; HRESULT hr; ULONG tag; LPSPropValue propval = NULL; msgtype_t msgtype = MSGTYPE_UNKNOWN; if (!message) { TRETURN msgtype; } if (get_gpgolmsgclass_tag (message, &tag) ) { TRETURN 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); TRETURN msgtype; } } else log_debug ("%s:%s: have override message class\n", SRCNAME, __func__); if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 ) { msgtype = string_to_type (propval->Value.lpszA); } MAPIFreeBuffer (propval); TRETURN 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) { TSTART; 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); TRETURN -1; } hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer, (STGM_CREATE | STGM_READWRITE), - (char*)filename, NULL, &stream); - if (FAILED (hr)) + (char*)filename, NULL, &stream); + if (FAILED (hr)) { log_error ("%s:%s: can't create file `%s': hr=%#lx\n", - SRCNAME, __func__, filename, hr); + SRCNAME, __func__, filename, hr); hr = -1; } else { hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP); if (FAILED (hr)) { log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx", SRCNAME, __func__, hr); stream->Revert (); hr = -1; } else { stream->Commit (0); hr = 0; } gpgol_release (stream); } gpgol_release (session); TRETURN 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) { TSTART; 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); TRETURN 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); TRETURN 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) { TSTART; 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); TRETURN -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); TRETURN rc; } /* Return the attachment method for attachment OBJ. In case of error we TRETURN 0 which happens not to be defined. */ static int get_attach_method (LPATTACH obj) { TSTART; 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); TRETURN 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); TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval; char *name = NULL; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval); - if (FAILED(hr)) + if (FAILED(hr)) hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval); if (FAILED(hr)) hr = HrGetOneProp ((LPMAPIPROP)obj, PR_DISPLAY_NAME_W, &propval); if (FAILED(hr)) { log_debug ("%s:%s: no filename property found", SRCNAME, __func__); TRETURN 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); TRETURN name; } /* Return the content-id of the attachment OBJ or NULL if it does not exists. Caller must free. */ static char * get_attach_content_id (LPATTACH obj) { TSTART; HRESULT hr; LPSPropValue propval = NULL; char *name; hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval); if (FAILED (hr)) { if (hr != MAPI_E_NOT_FOUND) log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx", SRCNAME, __func__, hr); TRETURN 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); TRETURN 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) { TSTART; 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); TRETURN 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); TRETURN 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) { TSTART; 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); TRETURN ATTACHTYPE_UNKNOWN; } retval = (attachtype_t)propval->Value.l; MAPIFreeBuffer (propval); TRETURN retval; } /* Gather information about attachments and TRETURN a new table of attachments. Caller must release the returned table.s The routine will TRETURN 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) { TSTART; HRESULT hr; SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} }; LPMAPITABLE mapitable; LPSRowSet mapirows; - mapi_attach_item_t *table; + mapi_attach_item_t *table; unsigned int pos, n_attach; ULONG moss_tag; if (get_gpgolattachtype_tag (message, &moss_tag) ) { TRETURN NULL; } /* Open the attachment table. */ hr = message->GetAttachmentTable (0, &mapitable); if (FAILED (hr)) { log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } memdbg_addRef (mapitable); hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum, NULL, NULL, 0, &mapirows); if (FAILED (hr)) { log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (mapitable); TRETURN NULL; } n_attach = mapirows->cRows > 0? mapirows->cRows : 0; log_debug ("%s:%s: message has %u attachments\n", SRCNAME, __func__, n_attach); if (!n_attach) { FreeProws (mapirows); gpgol_release (mapitable); TRETURN NULL; } /* Allocate our own table. */ table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table); - for (pos=0; pos < n_attach; pos++) + 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; } memdbg_addRef (att); table[pos].method = get_attach_method (att); table[pos].filename = fast? NULL : get_attach_filename (att); table[pos].content_type = fast? NULL : get_attach_mime_tag (att); table[pos].content_id = fast? NULL : get_attach_content_id (att); if (table[pos].content_type) { char *p = strchr (table[pos].content_type, ';'); if (p) { *p++ = 0; trim_trailing_spaces (table[pos].content_type); while (strchr (" \t\r\n", *p)) p++; trim_trailing_spaces (p); table[pos].content_type_parms = p; } } table[pos].attach_type = get_gpgolattachtype (att, moss_tag); gpgol_release (att); } table[0].private_mapitable = mapitable; FreeProws (mapirows); table[pos].end_of_table = 1; mapitable = NULL; if (fast) { log_debug ("%s:%s: attachment info: not shown due to fast flag\n", SRCNAME, __func__); } else { log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__); for (pos=0; !table[pos].end_of_table; pos++) { log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s' method:%d\n", table[pos].mapipos, table[pos].attach_type, anonstr (table[pos].filename), table[pos].content_type, table[pos].content_type_parms, table[pos].method); } } TRETURN table; } /* Release a table as created by mapi_create_attach_table. */ void mapi_release_attach_table (mapi_attach_item_t *table) { TSTART; unsigned int pos; LPMAPITABLE mapitable; if (!table) { TRETURN; } mapitable = (LPMAPITABLE)table[0].private_mapitable; if (mapitable) gpgol_release (mapitable); for (pos=0; !table[pos].end_of_table; pos++) { xfree (table[pos].filename); xfree (table[pos].content_type); xfree (table[pos].content_id); } xfree (table); TRETURN; } /* 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) { TSTART; HRESULT hr; LPATTACH att; LPSTREAM stream; if (r_attach) *r_attach = NULL; if (!item || item->end_of_table || item->mapipos == -1) { TRETURN 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); TRETURN NULL; } memdbg_addRef (att); if (item->method != ATTACH_BY_VALUE) { log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__); gpgol_release (att); TRETURN NULL; } hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN*) &stream); if (FAILED (hr)) { log_error ("%s:%s: can't open data stream of attachment: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (att); TRETURN NULL; } if (r_attach) *r_attach = att; else gpgol_release (att); TRETURN 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) { TSTART; HRESULT hr; LPSTREAM stream; STATSTG statInfo; ULONG nread; char *buffer; hr = gpgol_openProperty (att, 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); TRETURN NULL; } hr = stream->Stat (&statInfo, STATFLAG_NONAME); if ( hr != S_OK ) { log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr); gpgol_release (stream); TRETURN NULL; } /* Allocate one byte more so that we can terminate the string. */ buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1); hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread); if ( hr != S_OK ) { log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr); xfree (buffer); gpgol_release (stream); TRETURN NULL; } if (nread != statInfo.cbSize.QuadPart) { log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__); xfree (buffer); buffer = NULL; } gpgol_release (stream); /* Make sure that the buffer is a C string. */ if (buffer) buffer[nread] = 0; if (r_nbytes) *r_nbytes = nread; TRETURN buffer; } /* Return an attachment as a malloced buffer. The size of the buffer will be stored at R_NBYTES. If unprotect is true, the atatchment will be unprotected. Returns NULL on failure. */ char * mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes) { TSTART; HRESULT hr; LPATTACH att; char *buffer; if (!item || item->end_of_table || item->mapipos == -1) { TRETURN 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); TRETURN NULL; } memdbg_addRef (att); if (item->method != ATTACH_BY_VALUE) { log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__); gpgol_release (att); TRETURN NULL; } buffer = attach_to_buffer (att, r_nbytes); gpgol_release (att); TRETURN buffer; } /* Mark this attachment as the original MOSS message. We set a custom property as well as the hidden flag. */ -int +int mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item) { TSTART; int retval = -1; HRESULT hr; LPATTACH att; SPropValue prop; if (!item || item->end_of_table || item->mapipos == -1) { TRETURN -1; } log_debug ("%s:%s: Marking %i as MOSS attachment", SRCNAME, __func__, item->mapipos); 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); TRETURN -1; } memdbg_addRef (att); if (get_gpgolattachtype_tag (message, &prop.ulPropTag) ) goto leave; prop.Value.l = ATTACHTYPE_MOSS; - hr = HrSetOneProp (att, &prop); + hr = HrSetOneProp (att, &prop); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", - SRCNAME, __func__, "GpgOL Attach Type", hr); + SRCNAME, __func__, "GpgOL Attach Type", hr); TRETURN 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); + 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); + SRCNAME, __func__, hr); goto leave; } - + retval = 0; - + leave: gpgol_release (att); TRETURN retval; } /* If the hidden property has not been set on ATTACH, set it and save the changes. */ -int +int mapi_set_attach_hidden (LPATTACH attach) { TSTART; int retval = -1; HRESULT hr; LPSPropValue propval; SPropValue prop; hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval); - if (SUCCEEDED (hr) + if (SUCCEEDED (hr) && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b) { TRETURN 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); + 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); + SRCNAME, __func__, hr); goto leave; } - + retval = 0; - + leave: TRETURN retval; } /* Returns true if ATTACH has the hidden flag set to true. */ int mapi_test_attach_hidden (LPATTACH attach) { TSTART; HRESULT hr; LPSPropValue propval = NULL; int result = 0; - + hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval); if (FAILED (hr)) { TRETURN result; /* No. */ } - + if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b) result = 1; /* Yes. */ MAPIFreeBuffer (propval); TRETURN result; } /* 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 +int mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name) { TSTART; HRESULT hr; SPropValue prop; if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) ) { TRETURN -1; } prop.Value.lpszA = xstrdup (name); - hr = HrSetOneProp (message, &prop); + 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); + SRCNAME, __func__, "GpgOL Msg Class", hr); TRETURN -1; } TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgolcharset_tag (obj, &tag) ) { TRETURN NULL; /* Error. */ } hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval); if (FAILED (hr)) { TRETURN 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); TRETURN retstr; } -/* Set the GpgOl charset property to an attachment. +/* Set the GpgOl charset property to an attachment. Note that this function does not call SaveChanges. */ -int +int mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset) { TSTART; 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) ) { TRETURN -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); + 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); + SRCNAME, __func__, "GpgOL Charset", hr); TRETURN -1; } TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgoldraftinfo_tag (msg, &tag) ) { TRETURN NULL; } hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) { TRETURN NULL; } if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) retstr = xstrdup (propval->Value.lpszA); else retstr = NULL; MAPIFreeBuffer (propval); TRETURN 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 + 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 +int mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string) { TSTART; HRESULT hr; SPropValue prop; SPropTagArray proparray; if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) ) { TRETURN -1; } if (string) { prop.Value.lpszA = xstrdup (string); - hr = HrSetOneProp (message, &prop); + 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); + "GpgOL Draft Info", hr); TRETURN -1; } TRETURN 0; } /* Return the MIME info as an allocated string. Will never return NULL. */ char * mapi_get_mime_info (LPMESSAGE msg) { TSTART; HRESULT hr; LPSPropValue propval = NULL; ULONG tag; char *retstr; if (get_gpgolmimeinfo_tag (msg, &tag) ) { TRETURN xstrdup ("[Error getting tag for MIME info]"); } hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval); if (FAILED (hr)) { TRETURN xstrdup (""); } if (PROP_TYPE (propval->ulPropTag) == PT_STRING8) retstr = xstrdup (propval->Value.lpszA); else retstr = xstrdup ("[MIME info has an invalid type]"); MAPIFreeBuffer (propval); TRETURN 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) { TSTART; char *buf = mapi_get_gpgol_draft_info (message); int ret = 0; if (!buf) { TRETURN 0; } if (buf[0] == 'E') { ret |= 1; } if (buf[1] == 'S') { ret |= 2; } xfree (buf); TRETURN 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) { TSTART; char buf[4]; buf[3] = '\0'; buf[2] = 'A'; /* Protocol */ buf[1] = flags & 2 ? 'S' : 's'; buf[0] = flags & 1 ? 'E' : 'e'; TRETURN mapi_set_gpgol_draft_info (message, buf); } /* Helper for mapi_get_msg_content_type() */ static int parse_headers_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; } /* Parses a std::string and returns an rfc822_parse_t with the result. Can return NULL on error. Caller must free the returned context. r_is_wks is set to true in case of a WKS Confirmation mail. */ static rfc822parse_t parse_header_data (const std::string &hdrStr, bool &r_is_wks) { TSTART; rfc822parse_t msg = nullptr; if (hdrStr.empty()) { log_error ("%s:%s: called with empty string", SRCNAME, __func__); TRETURN nullptr; } /* Read the headers into an rfc822 object. */ msg = rfc822parse_open (parse_headers_cb, NULL); if (!msg) { log_error ("%s:%s: rfc822parse_open failed", SRCNAME, __func__); TRETURN nullptr; } const char *s = nullptr; const char *header_lines = hdrStr.c_str(); size_t length; while ((s = strchr (header_lines, '\n'))) { length = (s - header_lines); if (length && s[-1] == '\r') length--; if (!strncmp ("Wks-Phase: confirm", header_lines, std::max (18, (int) length))) { log_debug ("%s:%s: detected wks confirmation mail", SRCNAME, __func__); rfc822parse_close (msg); r_is_wks = true; TRETURN nullptr; } rfc822parse_insert (msg, (const unsigned char*)header_lines, length); header_lines = s+1; } TRETURN msg; } /* 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) { TSTART; const char *s; char *retstr = NULL; bool isWks = false; if (r_protocol) *r_protocol = NULL; if (r_smtype) *r_smtype = NULL; std::string hdrStr = mapi_get_header (message); if (hdrStr.empty()) { log_debug ("%s:%s: failed to get headers. Looking at first attach", SRCNAME, __func__); hdrStr = mapi_get_first_attach_data (message); if (hdrStr.empty()) { log_error ("%s:%s: failed to get headers. And attachment.", SRCNAME, __func__); TRETURN NULL; } } rfc822parse_t msg = parse_header_data (hdrStr, isWks); if (!msg) { if (isWks) { TRETURN xstrdup ("wks.confirmation.mail"); } log_error ("%s:%s: failed to parse headers", SRCNAME, __func__); TRETURN NULL; } /* Parse the content-type field. */ rfc822parse_field_t ctx = rfc822parse_parse_field (msg, "Content-Type", -1); if (ctx) { const char *s1, *s2; rfc822parse_t tnef_msg = nullptr; 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 (!strcmp (retstr, "application/ms-tnef")) { char *attach_mime = get_first_attach_mime_tag (message); if (attach_mime && !strcmp (attach_mime, "multipart/signed")) { log_debug ("%s:%s: Found multipart signed ms-tnef mail.", SRCNAME, __func__); /* Let's look in the first attachment for the real message to figure out the real content type. */ const auto attach_data = mapi_get_first_attach_data (message); tnef_msg = parse_header_data (attach_data, isWks); if (tnef_msg) { rfc822parse_field_t ctx2; ctx2 = rfc822parse_parse_field (tnef_msg, "Content-Type", -1); if (ctx2) { s1 = rfc822parse_query_media_type (ctx2, &s2); if (s1) { xfree (retstr); retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1); strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2); rfc822parse_release_field (ctx); ctx = ctx2; } else { log_error ("%s:%s: Failed to find ct in ms-tnef.", SRCNAME, __func__); rfc822parse_release_field (ctx2); } } } else { log_error ("%s:%s: Failed to parse ms-tnef.", SRCNAME, __func__); if (isWks) { xfree (retstr); rfc822parse_close (msg); TRETURN xstrdup ("wks.confirmation.mail"); } } } xfree (attach_mime); } 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); if (tnef_msg) { rfc822parse_close (tnef_msg); } } rfc822parse_close (msg); TRETURN 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) { TSTART; HRESULT hr; LPSPropValue propval = NULL; ULONG tag; int yes = 0; - + if (get_gpgollastdecrypted_tag (message, &tag) ) { TRETURN 0; /* No. */ } hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval); if (FAILED (hr)) { TRETURN 0; /* No. */ } - + if (PROP_TYPE (propval->ulPropTag) == PT_BINARY) yes = 1; MAPIFreeBuffer (propval); TRETURN 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) { TSTART; 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)) { TRETURN 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); TRETURN yes; } /* Copy the MAPI body to a PGPBODY type attachment. */ int mapi_body_to_attachment (LPMESSAGE message) { TSTART; HRESULT hr; LPSTREAM instream; ULONG newpos; LPATTACH newatt = NULL; SPropValue prop; LPSTREAM outstream = NULL; LPUNKNOWN punk; char body_filename[] = PGPBODYFILENAME; instream = mapi_get_body_as_stream (message); if (!instream) { TRETURN -1; } log_debug ("%s:%s: Creating MOSS body attachment", SRCNAME, __func__); hr = message->CreateAttach (NULL, 0, &newpos, &newatt); if (hr) { log_error ("%s:%s: can't create attachment: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } prop.ulPropTag = PR_ATTACH_METHOD; prop.Value.ul = ATTACH_BY_VALUE; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set attach method: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } /* Mark that attachment so that we know why it has been created. */ if (get_gpgolattachtype_tag (message, &prop.ulPropTag) ) goto leave; prop.Value.l = ATTACHTYPE_PGPBODY; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set %s property: hr=%#lx\n", SRCNAME, __func__, "GpgOL Attach Type", hr); goto leave; } prop.ulPropTag = PR_ATTACHMENT_HIDDEN; prop.Value.b = TRUE; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } prop.ulPropTag = PR_ATTACH_FILENAME_A; prop.Value.lpszA = body_filename; hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop); if (hr) { log_error ("%s:%s: can't set attach filename: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } punk = (LPUNKNOWN)outstream; hr = gpgol_openProperty (newatt, PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_CREATE|MAPI_MODIFY, &punk); if (FAILED (hr)) { log_error ("%s:%s: can't create output stream: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } outstream = (LPSTREAM)punk; /* Insert a blank line so that our mime parser skips over the mail headers. */ hr = outstream->Write ("\r\n", 2, NULL); if (hr) { log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } { ULARGE_INTEGER cb; cb.QuadPart = 0xffffffffffffffffll; hr = instream->CopyTo (outstream, cb, NULL, NULL); } if (hr) { log_error ("%s:%s: can't copy streams: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } hr = outstream->Commit (0); if (hr) { log_error ("%s:%s: Commiting output stream failed: hr=%#lx", SRCNAME, __func__, hr); goto leave; } gpgol_release (outstream); outstream = NULL; hr = newatt->SaveChanges (0); if (hr) { log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n", SRCNAME, __func__, hr); goto leave; } gpgol_release (newatt); newatt = NULL; hr = mapi_save_changes (message, KEEP_OPEN_READWRITE); leave: if (outstream) { outstream->Revert (); gpgol_release (outstream); } if (newatt) gpgol_release (newatt); gpgol_release (instream); TRETURN hr? -1:0; } static int hide_attachment_mapipos (LPMESSAGE message, int pos) { LPATTACH att = nullptr; HRESULT hr = message->OpenAttach (pos, nullptr, MAPI_BEST_ACCESS, &att); if (hr != S_OK) { log_error ("%s:%s: can't open attachment at %d hr=%#lx", SRCNAME, __func__, pos, hr); TRETURN -1; } memdbg_addRef (att); log_debug ("%s:%s: Hiding attachment %i.", SRCNAME, __func__, pos); mapi_set_attach_hidden (att); gpgol_release (att); return 0; } int mapi_mark_or_create_moss_attach (LPMESSAGE message, LPMESSAGE parsed_message, msgtype_t msgtype) { TSTART; int i; if (msgtype == MSGTYPE_UNKNOWN || msgtype == MSGTYPE_GPGOL) { TRETURN 0; } /* First check if we already have one marked. */ mapi_attach_item_t *table = mapi_create_attach_table (message, 0); int part1 = 0, part2 = 0; int have_mosstempl = 0; for (i = 0; table && !table[i].end_of_table; i++) { have_mosstempl |= (table[i].attach_type == ATTACHTYPE_MOSSTEMPL); if (table[i].attach_type == ATTACHTYPE_PGPBODY || table[i].attach_type == ATTACHTYPE_MOSS || table[i].attach_type == ATTACHTYPE_MOSSTEMPL) { if (!part1) { part1 = i + 1; if (have_mosstempl) { /* If we have a MOSSTEMPL use that one. */ break; } } else if (!part2) { /* If we have two MOSS attachments we use the second one if the first one is not the octet-stream. */ if (table[part1 - 1].content_type && !strcmp (table[part1 - 1].content_type, "application/octet-stream")) { /* This is rare so debug it. Happend when drafts were encrypted / decrypted and another client was running */ log_debug ("%s:%s: Have two MOSS attachments but the " "first one is the octet stream", SRCNAME, __func__); int tmp = part1; part1 = i + 1; part2 = tmp; break; } part2 = i + 1; break; } } } if (have_mosstempl && parsed_message) { /* If we have a mosstempl. This means that we created this message. Sometimes we get the situation in sent mails that we have a mosstempl but the pgp version header and octet streams also attached in the parsed_message. We can't trigger a reread from the base message so in that case we want to hide everything as we rebuild the message from our MOSS. We don't delete here to avoid bugs where data loss might occur. See also: T4241 */ log_debug ("%s:%s: Found mosstempl. Hiding all other attachments.", SRCNAME, __func__); mapi_attach_item_t *table2 = mapi_create_attach_table (parsed_message, 0); for (i = 0; table2 && !table2[i].end_of_table; i++) { hide_attachment_mapipos (parsed_message, table2[i].mapipos); } if (table2) { mapi_release_attach_table (table2); /* GpgOL works on the OOM so we have to use the MOSS parts from the parsed message. */ int parsed_part = mapi_mark_or_create_moss_attach (parsed_message, nullptr, msgtype); if (parsed_part) { mapi_release_attach_table (table); TRETURN parsed_part; } } } if (part1 || part2) { /* Remark to ensure that it is hidden. As our revert code must unhide it so that it is not stored in winmail.dat but used as the mosstmpl. */ mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1); hide_attachment_mapipos (message, item->mapipos); mapi_release_attach_table (table); TRETURN part2 ? part2 : part1; } if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED || msgtype == MSGTYPE_GPGOL_PGP_MESSAGE) { /* Inline message we need to create body attachment so that we are able to restore the content. */ if (mapi_body_to_attachment (message)) { log_error ("%s:%s: Failed to create body attachment.", SRCNAME, __func__); TRETURN 0; } log_debug ("%s:%s: Created body attachment. Repeating lookup.", SRCNAME, __func__); /* The position of the MOSS attach might change depending on the attachment count of the mail. So repeat the check to get the right position. */ mapi_release_attach_table (table); TRETURN mapi_mark_or_create_moss_attach (message, parsed_message, msgtype); } if (!table) { log_debug ("%s:%s: Neither pgp inline nor an attachment table.", SRCNAME, __func__); TRETURN 0; } /* MIME Mails check for S/MIME first. */ for (i = 0; !table[i].end_of_table; i++) { if (table[i].content_type && (!strcmp (table[i].content_type, "application/pkcs7-mime") || !strcmp (table[i].content_type, "application/x-pkcs7-mime")) && table[i].filename && !strcmp (table[i].filename, "smime.p7m")) break; } if (!table[i].end_of_table) { mapi_mark_moss_attach (message, table + i); mapi_release_attach_table (table); TRETURN i + 1; } /* PGP/MIME or S/MIME stuff. */ /* Multipart/encrypted message: We expect 2 attachments. The first one with the version number and the second one with the ciphertext. As we don't know wether we are called the first time, we first try to find these attachments by looking at all attachments. Only if this fails we identify them by their order (i.e. the first 2 attachments) and mark them as part1 and part2. */ for (i = 0; !table[i].end_of_table; i++); /* Count entries */ if (i >= 2) { int part1_idx = -1, part2_idx = -1; /* At least 2 attachments but none are marked. Thus we assume that this is the first time we see this message and we will set the mark now if we see appropriate content types. */ if (table[0].content_type && !strcmp (table[0].content_type, "application/pgp-encrypted")) part1_idx = 0; if (table[1].content_type && !strcmp (table[1].content_type, "application/octet-stream")) part2_idx = 1; if (part1_idx != -1 && part2_idx != -1) { mapi_mark_moss_attach (message, table+part1_idx); mapi_mark_moss_attach (message, table+part2_idx); mapi_release_attach_table (table); TRETURN 2; } } if (!table[0].end_of_table && table[1].end_of_table) { /* No MOSS flag found in the table but there is only one attachment. Due to the message type we know that this is the original MOSS message. We mark this attachment as hidden, so that it won't get displayed. We further mark it as our original MOSS attachment so that after parsing we have a mean to find it again (see above). */ mapi_mark_moss_attach (message, table + 0); mapi_release_attach_table (table); TRETURN 1; } mapi_release_attach_table (table); TRETURN 0; /* No original attachment - this should not happen. */ } static std::string ac_get_value (const char *header, const char *what) { TSTART; if (!header || !what) { STRANGEPOINT; TRETURN std::string(); } const char *s = strstr (header, what); if (!s) { log_debug ("%s:%s: could not find %s in header", SRCNAME, __func__, what); TRETURN std::string(); } /* As we found it we can be sure that this is not out of bounds. */ s += strlen (what); if (*s != '=') { log_debug ("%s:%s: No equal sign after %s in header %s", SRCNAME, __func__, what, s); TRETURN std::string(); } /* Move over the = sign. */ s++; /* Find the sep */ const char *s2 = strchr (s, ';'); if (!s2) { /* No seperator found. Assume the rest is the value */ TRETURN s; } /* From the equal to the ; is our value. */ TRETURN std::string (s, s2 - s); } static GpgME::Data prepare_key_data (const std::string &d) { TSTART; if (d.empty()) { STRANGEPOINT; TRETURN GpgME::Data(); } /* Prepare the keydata */ b64_state_t base64; /* The state of the Base-64 decoder. */ b64_init (&base64); /* strdup and not xstrdup as we want GpgME to take over */ char *b64decoded = strdup (d.c_str()); size_t len = b64_decode (&base64, b64decoded, strlen(b64decoded)); if (!len) { log_error ("%s:%s: Invalid base64 in %s", SRCNAME, __func__, b64decoded); xfree (b64decoded); TRETURN GpgME::Data(); } auto data = GpgME::Data (b64decoded, len, false /* take ownership */); TRETURN data; } bool mapi_get_header_info (LPMESSAGE message, header_info_s &r_header_info) { TSTART; rfc822parse_t msg; autocrypt_s r_autocrypt; r_autocrypt.exists = false; /* Read the headers into an rfc822 object. */ msg = rfc822parse_open (parse_headers_cb, NULL); if (!msg) { log_error ("%s:%s: rfc822parse_open failed", SRCNAME, __func__); TRETURN false; } const std::string hdrStr = mapi_get_header (message); if (hdrStr.empty()) { log_error ("%s:%s: failed to get headers", SRCNAME, __func__); rfc822parse_close (msg); TRETURN false; } size_t length; const char *header_lines = hdrStr.c_str(); const char *s; 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; } const char *ac_field = rfc822parse_get_field (msg, "Autocrypt", -1, 0); if (ac_field) { r_autocrypt.exists = true; r_autocrypt.addr = ac_get_value (ac_field, "addr"); r_autocrypt.data = prepare_key_data (ac_get_value (ac_field, "keydata")); r_autocrypt.pref = ac_get_value (ac_field, "prefer-encrypt"); } rfc822parse_field_t field = rfc822parse_parse_field (msg, "Content-Type", -1); if (field) { const char *boundary = rfc822parse_query_parameter (field, "boundary", -1); if (boundary) { r_header_info.boundary = boundary; log_dbg ("Found Boundary. '%s'", boundary); } else { log_dbg ("Failed to find top level boundary."); } rfc822parse_release_field (field); } else { log_dbg ("Failed to get content type field."); } r_header_info.acInfo = r_autocrypt; rfc822parse_close (msg); TRETURN true; } void mapi_delete_gpgol_tags (LPMESSAGE message) { if (!message) { STRANGEPOINT; return; } ULONG tag; if (!get_gpgolmsgclass_tag (message, &tag)) { HRESULT hr; SPropTagArray proparray; proparray.cValues = 1; proparray.aulPropTag[0] = tag; hr = message->DeleteProps (&proparray, NULL); if (hr) { log_error ("%s:%s: deleteprops failed: hr=%#lx\n", SRCNAME, __func__, hr); } } } void mapi_set_mesage_class (LPMESSAGE message, const char *cls) { if (!message || !cls) { STRANGEPOINT; return; } HRESULT hr; SPropValue prop; prop.ulPropTag = PR_MESSAGE_CLASS_A; prop.Value.lpszA = xstrdup (cls); hr = HrSetOneProp (message, &prop); xfree (prop.Value.lpszA); if (hr) { log_error ("%s:%s: set msg class failed: hr=%#lx\n", SRCNAME, __func__, hr); } hr = mapi_save_changes (message, KEEP_OPEN_READWRITE); if (hr) { log_error ("%s:%s: save failed: hr=%#lx\n", SRCNAME, __func__, hr); } } diff --git a/src/mapihelp.h b/src/mapihelp.h index b131c97..beb2ab1 100644 --- a/src/mapihelp.h +++ b/src/mapihelp.h @@ -1,145 +1,145 @@ /* mapihelp.h - 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 . */ #ifndef MAPIHELP_H #define MAPIHELP_H #ifdef HAVE_CONFIG_H #include #endif #include #ifdef __cplusplus extern "C" { #endif /* The filename of the attachment we create as the result of sign or encrypt operations. */ #define MIMEATTACHFILENAME "GpgOL_MIME_structure.txt" /* The name of the file we use to store the original body of PGP encrypted messages. Note that PGP/MIME message don't need that because Outlook carries them as 2 attachments. */ #define PGPBODYFILENAME "GpgOL_original_OpenPGP_message.txt" void log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname); int get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag); int get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag); int get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag); int get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag); int get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag); int get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag); int mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body, const char *dbg_file, const char *dbg_func); #define mapi_save_changes(a,b) \ mapi_do_save_changes ((a),(b), 0, __FILE__, __func__) #define mapi_delete_body_props(a,b) \ mapi_do_save_changes ((a),(b), 1, __FILE__, __func__) int mapi_set_header (LPMESSAGE msg, const char *name, const char *val); int mapi_change_message_class (LPMESSAGE message, int sync_override, msgtype_t *r_type); char *mapi_get_message_class (LPMESSAGE message); char *mapi_get_old_message_class (LPMESSAGE message); char *mapi_get_sender (LPMESSAGE message); msgtype_t mapi_get_message_type (LPMESSAGE message); int mapi_to_mime (LPMESSAGE message, const char *filename); char *mapi_get_binary_prop (LPMESSAGE message,ULONG proptype,size_t *r_nbytes); int mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value); char *mapi_get_from_address (LPMESSAGE message); char *mapi_get_subject (LPMESSAGE message); LPSTREAM mapi_get_body_as_stream (LPMESSAGE message); char *mapi_get_body (LPMESSAGE message, size_t *r_nbytes); mapi_attach_item_t *mapi_create_attach_table (LPMESSAGE message, int fast); void mapi_release_attach_table (mapi_attach_item_t *table); -LPSTREAM mapi_get_attach_as_stream (LPMESSAGE message, - mapi_attach_item_t *item, +LPSTREAM mapi_get_attach_as_stream (LPMESSAGE message, + mapi_attach_item_t *item, LPATTACH *r_attach); char *mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes); int mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item); int mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name); char *mapi_get_gpgol_charset (LPMESSAGE obj); int mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset); char *mapi_get_gpgol_draft_info (LPMESSAGE msg); int mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string); int mapi_set_attach_hidden (LPATTACH attach); int mapi_test_attach_hidden (LPATTACH attach); char *mapi_get_mime_info (LPMESSAGE msg); -char *mapi_get_message_content_type (LPMESSAGE message, +char *mapi_get_message_content_type (LPMESSAGE message, char **r_protocol, char **r_smtype); int mapi_has_last_decrypted (LPMESSAGE message); attachtype_t get_gpgolattachtype (LPATTACH obj, ULONG tag); int get_gpgol_draft_info_flags (LPMESSAGE message); int set_gpgol_draft_info_flags (LPMESSAGE message, int flags); /* Mark crypto attachments as hidden. And mark the moss attachment for later use. Returns the attachments position (1 is the first attachment) or 0 in case no attachment was found. */ int mapi_mark_or_create_moss_attach (LPMESSAGE base_message, LPMESSAGE parsed_message, msgtype_t msgtype); /* Copy the MAPI body to a PGPBODY type attachment. */ int mapi_body_to_attachment (LPMESSAGE message); /* Get malloced uid of a message */ char * mapi_get_uid (LPMESSAGE message); /* Remove the gpgol specific mapi tags */ void mapi_delete_gpgol_tags (LPMESSAGE message); /* Explicitly change the message class */ void mapi_set_mesage_class (LPMESSAGE message, const char *cls); #ifdef __cplusplus } #include /* Parse the headers for additional useful fields. r_autocrypt_info: Information about the autocrypt header. r_header_info: Just a generic struct for other stuff. A return value of false indicates an error. Check the individual info fields "exists" values to check if the header exists in the message */ bool mapi_get_header_info (LPMESSAGE message, header_info_s &r_header_info); std::string mapi_get_header (LPMESSAGE message); #endif #endif /*MAPIHELP_H*/ diff --git a/src/mimemaker.h b/src/mimemaker.h index 22c893f..f92501b 100644 --- a/src/mimemaker.h +++ b/src/mimemaker.h @@ -1,104 +1,104 @@ /* mimemaker.h - Construct MIME from MAPI * Copyright (C) 2007, 2008 g10 Code GmbH * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. - * + * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef MIMEMAKER_H #define MIMEMAKER_H #include "mapihelp.h" class Mail; #ifdef __cplusplus extern "C" { #if 0 } #endif #endif /* Names for our attachments */ #define OPENPGP_ENC_NAME "openpgp-encrypted-message.asc" #define OPENPGP_SIG_NAME "openpgp-digital-signature.asc" #define SMIME_SIG_NAME "smime.p7s" /* The object we use instead of IStream. It allows us to have a callback method for output and thus for processing stuff recursively. */ struct sink_s; typedef struct sink_s *sink_t; struct sink_s { void *cb_data; sink_t extrasink; int (*writefnc)(sink_t sink, const void *data, size_t datalen); unsigned long enc_counter; /* Used by write_buffer_for_cb. */ /* struct { */ /* int idx; */ /* unsigned char inbuf[4]; */ /* int quads; */ /* } b64; */ }; int sink_std_write (sink_t sink, const void *data, size_t datalen); int sink_file_write (sink_t sink, const void *data, size_t datalen); int sink_encryption_write (sink_t encsink, const void *data, size_t datalen); int write_buffer_for_cb (void *opaque, const void *data, size_t datalen); int write_buffer (sink_t sink, const void *data, size_t datalen); /** @brief Try to restore a message from the moss attachment. * * Try to turn the moss attachment back into a Mail that other * MUAs could handle. Uses all the tricks available to archive * that. Returns 0 on success. */ int restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att, msgtype_t type, char *msgcls); int count_usable_attachments (mapi_attach_item_t *table); int add_body_and_attachments (sink_t sink, LPMESSAGE message, mapi_attach_item_t *att_table, Mail *mail, const char *body, int n_att_usable); int create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary, bool is_inline = false, int exchange_major_version = -1); /* Helper to write a boundary to the output sink. The leading LF will be written as well. */ int write_boundary (sink_t sink, const char *boundary, int lastone); LPATTACH create_mapi_attachment (LPMESSAGE message, sink_t sink, const char *overrideMimeTag = nullptr); int close_mapi_attachment (LPATTACH *attach, sink_t sink); int finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table, protocol_t protocol, int encrypt, bool is_inline = false, bool is_draft = false, int exchange_major_version = -1); void cancel_mapi_attachment (LPATTACH *attach, sink_t sink); void create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol, int first, const char *boundary, const char *micalg); int write_string (sink_t sink, const char *text); int write_b64 (sink_t sink, const void *data, size_t datalen); /* Encode an input string according to rfc2047 caller needs to free result. */ char *utf8_to_rfc2047b (const char *input); #ifdef __cplusplus } #endif #endif /*MIMEMAKER_H*/ diff --git a/src/olflange.h b/src/olflange.h index 9ad943d..3b31220 100644 --- a/src/olflange.h +++ b/src/olflange.h @@ -1,48 +1,48 @@ /* olflange.h - Flange between Outlook and the MapiGPGME class * 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 . */ #ifndef OLFLANGE_H #define OLFLANGE_H #include "mymapi.h" #include "mymapitags.h" #include "mapihelp.h" /* The GUID for this plugin. */ #define CLSIDSTR_GPGOL "{42d30988-1a3a-11da-c687-000d6080e735}" DEFINE_GUID(CLSID_GPGOL, 0x42d30988, 0x1a3a, 0x11da, 0xc6, 0x87, 0x00, 0x0d, 0x60, 0x80, 0xe7, 0x35); /* For documentation: The GUID used for our custom properties: {31805ab8-3e92-11dc-879c-00061b031004} */ /* The ProgID used by us */ #define GPGOL_PROGID "GNU.GpgOL" /* User friendly add in name */ #define GPGOL_PRETTY "GpgOL - The GnuPG Outlook Plugin" /* Short description of the addin */ #define GPGOL_DESCRIPTION "Cryptography for Outlook" EXTERN_C const char * __stdcall gpgol_check_version (const char *req_version); EXTERN_C int get_ol_main_version (void); void install_forms (void); #endif /*OLFLANGE_H*/ diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp index a7be2ae..e49120e 100644 --- a/src/oomhelp.cpp +++ b/src/oomhelp.cpp @@ -1,3625 +1,3625 @@ /* oomhelp.cpp - Helper functions for the Outlook Object Model * Copyright (C) 2009 g10 Code GmbH * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "common.h" #include "oomhelp.h" #include "cpphelp.h" #include "gpgoladdin.h" #include "categorymanager.h" #include "recipient.h" HRESULT gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj) { HRESULT ret = pObj->QueryInterface (riid, ppvObj); if (ret) { log_debug ("%s:%s: QueryInterface failed hr=%#lx", SRCNAME, __func__, ret); } else if ((opt.enable_debug & DBG_MEMORY) && *ppvObj) { memdbg_addRef (*ppvObj); } return ret; } HRESULT gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN FAR * lppUnk) { HRESULT ret = obj->OpenProperty (ulPropTag, lpiid, ulInterfaceOptions, ulFlags, lppUnk); if (ret) { log_debug ("%s:%s: OpenProperty failed hr=%#lx %s", SRCNAME, __func__, ret, mapi_err_to_string (ret)); } else if ((opt.enable_debug & DBG_MEMORY) && *lppUnk) { memdbg_addRef (*lppUnk); log_debug ("%s:%s: OpenProperty on %p prop %lx result %p", SRCNAME, __func__, obj, ulPropTag, *lppUnk); } return ret; } /* Return a malloced string with the utf-8 encoded name of the object or NULL if not available. */ char * get_object_name (LPUNKNOWN obj) { TSTART; HRESULT hr; LPDISPATCH disp = NULL; LPTYPEINFO tinfo = NULL; BSTR bstrname; char *name = NULL; if (!obj) goto leave; /* We can't use gpgol_queryInterface here to avoid recursion */ hr = obj->QueryInterface (IID_IDispatch, (void **)&disp); if (!disp || hr != S_OK) goto leave; disp->GetTypeInfo (0, 0, &tinfo); if (!tinfo) { - log_debug ("%s:%s: no typeinfo found for object\n", + log_debug ("%s:%s: no typeinfo found for object\n", SRCNAME, __func__); goto leave; } bstrname = NULL; hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0); if (hr || !bstrname) - log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", + log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", SRCNAME, __func__, hr); if (bstrname) { name = wchar_to_utf8 (bstrname); SysFreeString (bstrname); } leave: if (tinfo) tinfo->Release (); if (disp) disp->Release (); TRETURN name; } std::string get_object_name_s (LPUNKNOWN obj) { char *name = get_object_name (obj); std::string ret = "(null)"; if (name) { ret = name; } xfree (name); return ret; } std::string get_object_name_s (shared_disp_t obj) { return get_object_name_s (obj.get ()); } /* Lookup the dispid of object PDISP for member NAME. Returns DISPID_UNKNOWN on error. */ DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name) { HRESULT hr; DISPID dispid; wchar_t *wname; if (!pDisp || !name) { TRETURN DISPID_UNKNOWN; /* Error: Invalid arg. */ } wname = utf8_to_wchar (name); if (!wname) { TRETURN DISPID_UNKNOWN;/* Error: Out of memory. */ } - hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, + hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, LOCALE_SYSTEM_DEFAULT, &dispid); xfree (wname); if (hr != S_OK || dispid == DISPID_UNKNOWN) log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n", SRCNAME, __func__, name, (int)dispid, (unsigned int)hr); if (hr != S_OK) dispid = DISPID_UNKNOWN; return dispid; } static void init_excepinfo (EXCEPINFO *err) { if (!err) { TRETURN; } err->wCode = 0; err->wReserved = 0; err->bstrSource = nullptr; err->bstrDescription = nullptr; err->bstrHelpFile = nullptr; err->dwHelpContext = 0; err->pvReserved = nullptr; err->pfnDeferredFillIn = nullptr; err->scode = 0; } void dump_excepinfo (EXCEPINFO err) { log_oom ("%s:%s: Exception: \n" " wCode: 0x%x\n" " wReserved: 0x%x\n" " source: %S\n" " desc: %S\n" " help: %S\n" " helpCtx: 0x%x\n" " deferredFill: %p\n" " scode: 0x%x\n", SRCNAME, __func__, (unsigned int) err.wCode, (unsigned int) err.wReserved, err.bstrSource ? err.bstrSource : L"null", err.bstrDescription ? err.bstrDescription : L"null", err.bstrHelpFile ? err.bstrDescription : L"null", (unsigned int) err.dwHelpContext, err.pfnDeferredFillIn, (unsigned int) err.scode); } /* Return the OOM object's IDispatch interface described by FULLNAME. Returns NULL if not found. PSTART is the object where the search starts. FULLNAME is a dot delimited sequence of object names. If an object name has a "(foo)" suffix this passes it as a parameter to the invoke function (i.e. using (DISPATCH|PROPERTYGET)). Object names including the optional suffix are truncated at 127 byte. */ LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname) { TSTART; HRESULT hr; LPDISPATCH pObj = pStart; LPDISPATCH pDisp = NULL; log_oom ("%s:%s: looking for %p->`%s'", SRCNAME, __func__, pStart, fullname); while (pObj) { DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT vtResult; wchar_t *wname; char name[128]; int n_parms = 0; BSTR parmstr = NULL; INT parmint = 0; DISPID dispid; char *p, *pend; int dispmethod; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); if (pDisp) { gpgol_release (pDisp); pDisp = NULL; } if (gpgol_queryInterface (pObj, IID_IDispatch, (LPVOID*)&pDisp) != S_OK) { log_error ("%s:%s Object does not support IDispatch", SRCNAME, __func__); if (pObj != pStart) gpgol_release (pObj); TRETURN NULL; } /* Confirmed through testing that the retval needs a release */ if (pObj != pStart) gpgol_release (pObj); pObj = NULL; if (!pDisp) { TRETURN NULL; /* The object has no IDispatch interface. */ } if (!*fullname) { if ((opt.enable_debug & DBG_MEMORY)) { pDisp->AddRef (); int ref = pDisp->Release (); log_oom ("%s:%s: got %p with %i refs", SRCNAME, __func__, pDisp, ref); } TRETURN pDisp; /* Ready. */ } - + /* Break out the next name part. */ { const char *dot; size_t n; - + dot = strchr (fullname, '.'); if (dot == fullname) { gpgol_release (pDisp); TRETURN NULL; /* Empty name part: error. */ } else if (dot) n = dot - fullname; else n = strlen (fullname); - + if (n >= sizeof name) n = sizeof name - 1; strncpy (name, fullname, n); name[n] = 0; - + if (dot) fullname = dot + 1; else fullname += strlen (fullname); } - + if (!strncmp (name, "get_", 4) && name[4]) { dispmethod = DISPATCH_PROPERTYGET; memmove (name, name+4, strlen (name+4)+1); } else if ((p = strchr (name, '('))) { *p++ = 0; pend = strchr (p, ')'); if (pend) *pend = 0; if (*p == ',' && p[1] != ',') { /* We assume this is "foo(,30007)". I.e. the frst arg is not given and the second one is an integer. */ parmint = (int)strtol (p+1, NULL, 10); n_parms = 4; } else { wname = utf8_to_wchar (p); if (wname) { parmstr = SysAllocString (wname); xfree (wname); } if (!parmstr) { gpgol_release (pDisp); TRETURN NULL; /* Error: Out of memory. */ } n_parms = 1; } dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET; } else dispmethod = DISPATCH_METHOD; /* Lookup the dispid. */ dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { if (parmstr) SysFreeString (parmstr); gpgol_release (pDisp); TRETURN NULL; /* Name not found. */ } /* Invoke the method. */ dispparams.rgvarg = aVariant; dispparams.cArgs = 0; if (n_parms) { if (n_parms == 4) { - dispparams.rgvarg[0].vt = VT_ERROR; - dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; - dispparams.rgvarg[1].vt = VT_ERROR; - dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; - dispparams.rgvarg[2].vt = VT_INT; - dispparams.rgvarg[2].intVal = parmint; - dispparams.rgvarg[3].vt = VT_ERROR; - dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[0].vt = VT_ERROR; + dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[1].vt = VT_ERROR; + dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[2].vt = VT_INT; + dispparams.rgvarg[2].intVal = parmint; + dispparams.rgvarg[3].vt = VT_ERROR; + dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = n_parms; } else if (n_parms == 1 && parmstr) { dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = parmstr; dispparams.cArgs++; } } dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, dispmethod, &dispparams, &vtResult, &execpinfo, &argErr); if (parmstr) SysFreeString (parmstr); if (hr != S_OK || vtResult.vt != VT_DISPATCH) { log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x dispid=0x%x", SRCNAME, __func__, name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr, (unsigned int)dispid); dump_excepinfo (execpinfo); VariantClear (&vtResult); gpgol_release (pDisp); TRETURN NULL; /* Invoke failed. */ } pObj = vtResult.pdispVal; memdbg_addRef (pObj); } gpgol_release (pDisp); log_debug ("%s:%s: no object", SRCNAME, __func__); TRETURN NULL; } shared_disp_t get_oom_object_s (shared_disp_t pStart, const char *fullname) { return MAKE_SHARED (get_oom_object (pStart.get (), fullname)); } shared_disp_t get_oom_object_s (LPDISPATCH pStart, const char *fullname) { return MAKE_SHARED (get_oom_object (pStart, fullname)); } /* Helper for put_oom_icon. */ static int put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask) { TSTART; HRESULT hr; PICTDESC pdesc; LPDISPATCH pPict; DISPID dispid_put = DISPID_PROPERTYPUT; UINT fuload; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[2]; /* When loading the mask we need to set the monochrome flag. We better create a DIB section to avoid possible rendering problems. */ fuload = LR_CREATEDIBSECTION | LR_SHARED; if (is_mask) fuload |= LR_MONOCHROME; - + memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst, MAKEINTRESOURCE (resource), IMAGE_BITMAP, size, size, fuload); if (!pdesc.bmp.hbitmap) { - log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", + log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", SRCNAME, __func__, resource); TRETURN -1; } /* Wrap the image into an OLE object. */ - hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, + 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); TRETURN -1; } - + /* Store to the Picture or Mask property of the CommandBarButton. */ dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = pPict; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr); TRETURN -1; } TRETURN 0; } /* Update the icon of PDISP using the bitmap with RESOURCE ID. The function adds the system pixel size to the resource id to compute the actual icon size. The resource id of the mask is the N+1. */ int put_oom_icon (LPDISPATCH pDisp, int resource_id, int size) { TSTART; int rc; /* This code is only relevant for Outlook < 2010. Ideally it should grab the system pixel size and use an icon of the appropiate size (e.g. 32 or 64px) */ rc = put_picture_or_mask (pDisp, resource_id, size, 0); if (!rc) rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1); TRETURN rc; } /* Set the boolean property NAME to VALUE. */ int put_oom_bool (LPDISPATCH pDisp, const char *name, int value) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { - log_debug ("%s:%s: Putting '%s' failed: %#lx", + log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); TRETURN -1; } TRETURN 0; } /* Set the property NAME to VALUE. */ int put_oom_int (LPDISPATCH pDisp, const char *name, int value) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = value; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { - log_debug ("%s:%s: Putting '%s' failed: %#lx", + log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); TRETURN -1; } TRETURN 0; } /* Set the property NAME to VALUE. */ int put_oom_array (LPDISPATCH pDisp, const char *name, unsigned char *value, size_t size) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); VariantInit (aVariant); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } /* Prepare the savearray */ SAFEARRAYBOUND saBound; saBound.lLbound = 0; saBound.cElements = size; SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, &saBound); if (!psa) { log_err ("Failed to create SafeArray"); TRETURN -1; } hr = SafeArrayLock(psa); if (!SUCCEEDED (hr)) { log_err ("Failed to lock array."); } memcpy (psa->pvData, value, size); SafeArrayUnlock(psa); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_ARRAY; dispparams.rgvarg[0].parray = psa; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, &execpinfo, &argErr); SafeArrayDestroy(psa); if (hr != S_OK) { log_debug ("%s:%s: error: putting %s" " hr=0x%x argErr=0x%x", SRCNAME, __func__, name, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Set the property NAME to STRING. */ int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; BSTR bstring; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } { wchar_t *tmp = utf8_to_wchar (string); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); TRETURN -1; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = bstring; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, &execpinfo, NULL); SysFreeString (bstring); if (hr != S_OK) { - log_debug ("%s:%s: Putting '%s' failed: %#lx", + log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Set the property NAME to DISP. */ int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = disp; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, &dispparams, NULL, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Get the boolean property NAME of the object PDISP. Returns False if not found or if it is not a boolean property. */ int get_oom_bool (LPDISPATCH pDisp, const char *name) { TSTART; - HRESULT hr; + HRESULT hr; int result = 0; DISPID dispid; - + dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BOOL) log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = !!rVariant.boolVal; VariantClear (&rVariant); } TRETURN result; } /* Get the integer property NAME of the object PDISP. Returns 0 if not found or if it is not an integer property. */ int get_oom_int (LPDISPATCH pDisp, const char *name) { TSTART; - HRESULT hr; + HRESULT hr; int result = 0; DISPID dispid; - + dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4) log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = rVariant.intVal; VariantClear (&rVariant); } TRETURN result; } int get_oom_int (shared_disp_t pDisp, const char *name) { return get_oom_int (pDisp.get (), name); } int get_oom_dirty (LPDISPATCH pDisp) { TSTART; HRESULT hr; DISPID dispid = DISPID_DIRTY_RAT; DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET | DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Property dirty not found: %#lx", SRCNAME, __func__, hr); TRETURN -1; } return !!rVariant.bVal; } #if 0 int put_oom_dirty (LPDISPATCH pDisp, bool value) { TSTART; /* NOTE: I have found no scenario where this does not return the exception that the property is write protected. But we can never know when we need such an arcane function so I left it in. */ HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid = DISPID_DIRTY_RAT; DISPPARAMS dispparams; VARIANT aVariant[1]; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT | DISPATCH_METHOD, &dispparams, NULL, &execpinfo, &argErr); if (hr != S_OK) { log_debug ("%s:%s: error: invoking dirty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, nullptr, 0, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } #endif /* Get the string property NAME of the object PDISP. Returns NULL if not found or if it is not a string property. */ char * get_oom_string (LPDISPATCH pDisp, const char *name) { TSTART; - HRESULT hr; + HRESULT hr; char *result = NULL; DISPID dispid; - + dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BSTR) log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else if (rVariant.bstrVal) result = wchar_to_utf8 (rVariant.bstrVal); VariantClear (&rVariant); } TRETURN result; } std::string get_oom_string_s (LPDISPATCH pDisp, const char *name) { char *ret_c = get_oom_string (pDisp, name); std::string ret; if (ret_c) { ret = ret_c; xfree (ret_c); } return ret; } std::string get_oom_string_s (shared_disp_t pDisp, const char *name) { return get_oom_string_s (pDisp.get (), name); } /* Get the object property NAME of the object PDISP. Returns NULL if not found or if it is not an object perty. */ LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name) { TSTART; - HRESULT hr; + HRESULT hr; DISPID dispid; - + dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_UNKNOWN) log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else { memdbg_addRef (rVariant.punkVal); TRETURN rVariant.punkVal; } VariantClear (&rVariant); } TRETURN NULL; } /* Return the control object described by the tag property with value TAG. The object POBJ must support the FindControl method. Returns NULL if not found. */ LPDISPATCH get_oom_control_bytag (LPDISPATCH pDisp, const char *tag) { TSTART; - HRESULT hr; + HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT rVariant; BSTR bstring; LPDISPATCH result = NULL; dispid = lookup_oom_dispid (pDisp, "FindControl"); if (dispid == DISPID_UNKNOWN) { log_debug ("%s:%s: Object %p has no FindControl method", SRCNAME, __func__, pDisp); TRETURN NULL; } { wchar_t *tmp = utf8_to_wchar (tag); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); TRETURN NULL; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */ - dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[1].vt = VT_BSTR; /* Tag */ dispparams.rgvarg[1].bstrVal = bstring; dispparams.rgvarg[2].vt = VT_ERROR; /* Id */ dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR;/* Type */ - dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); SysFreeString (bstring); if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal) { gpgol_queryInterface (rVariant.pdispVal, IID_IDispatch, (LPVOID*)&result); gpgol_release (rVariant.pdispVal); if (!result) log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.", SRCNAME, __func__, tag); } else { log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx", SRCNAME, __func__, tag, rVariant.vt, hr); VariantClear (&rVariant); } TRETURN result; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ LPDISPATCH add_oom_button (LPDISPATCH pObj) { TSTART; - HRESULT hr; + HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; VARIANT rVariant; dispid = lookup_oom_dispid (pObj, "Add"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_TRUE; dispparams.rgvarg[1].vt = VT_ERROR; /* Before */ - dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[2].vt = VT_ERROR; /* Parameter */ - dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR; /* Id */ - dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; + dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[4].vt = VT_INT; /* Type */ dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON; dispparams.cArgs = 5; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal) { log_error ("%s:%s: Adding Control failed: %#lx - vt=%d", SRCNAME, __func__, hr, rVariant.vt); VariantClear (&rVariant); TRETURN NULL; } TRETURN rVariant.pdispVal; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ void del_oom_button (LPDISPATCH pObj) { TSTART; - HRESULT hr; + HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; dispid = lookup_oom_dispid (pObj, "Delete"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_FALSE; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) log_error ("%s:%s: Deleting Control failed: %#lx", SRCNAME, __func__, hr); TRETURN; } /* Gets the current contexts HWND. Returns NULL on error */ HWND get_oom_context_window (LPDISPATCH context) { TSTART; LPOLEWINDOW actExplorer; HWND ret = NULL; actExplorer = (LPOLEWINDOW) get_oom_object(context, "Application.ActiveExplorer"); if (actExplorer) actExplorer->GetWindow (&ret); else { log_debug ("%s:%s: Could not find active window", SRCNAME, __func__); } gpgol_release (actExplorer); TRETURN ret; } int put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value) { TSTART; LPDISPATCH propertyAccessor; VARIANT cVariant[2]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (propertyAccessor, "SetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find SetProperty DISPID", SRCNAME, __func__); TRETURN -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); /* Variant 0 carries the data. */ VariantInit (&cVariant[0]); if (VariantCopy (&cVariant[0], value)) { log_error ("%s:%s: Falied to copy value.", SRCNAME, __func__); TRETURN -1; } /* Variant 1 is the DASL as found out by experiments. */ VariantInit (&cVariant[1]); cVariant[1].vt = VT_BSTR; cVariant[1].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 2; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); gpgol_release (propertyAccessor); if (hr != S_OK) { log_debug ("%s:%s: failure: invoking SetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); VariantClear (&rVariant); dump_excepinfo (execpinfo); TRETURN -1; } VariantClear (&rVariant); TRETURN 0; } int put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value) { TSTART; wchar_t *w_value = utf8_to_wchar (value); BSTR b_value = SysAllocString(w_value); xfree (w_value); VARIANT var; VariantInit (&var); var.vt = VT_BSTR; var.bstrVal = b_value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); TRETURN ret; } int put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value) { TSTART; VARIANT var; VariantInit (&var); var.vt = VT_I4; var.lVal = value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); TRETURN ret; } /* Get a MAPI property through OOM using the PropertyAccessor * interface and the DASL Uid. Returns -1 on error. * Variant has to be cleared with VariantClear. * rVariant must be a pointer to a Variant. */ int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant) { TSTART; LPDISPATCH propertyAccessor; VARIANT cVariant[1]; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (propertyAccessor, "GetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find GetProperty DISPID", SRCNAME, __func__); TRETURN -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); cVariant[0].vt = VT_BSTR; cVariant[0].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; VariantInit (rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, rVariant, &execpinfo, &argErr); SysFreeString (b_property); gpgol_release (propertyAccessor); if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id)) { /* It often happens that mails don't have a uid by us e.g. if they are not crypto mails or just dont have one. This is not an error. */ log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant->pdispVal, rVariant->vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (rVariant); TRETURN -1; } TRETURN 0; } /* Get a property string by using the PropertyAccessor of pDisp * Returns NULL on error or a newly allocated result. */ char * get_pa_string (LPDISPATCH pDisp, const char *property) { TSTART; VARIANT rVariant; char *result = NULL; if (get_pa_variant (pDisp, property, &rVariant)) { TRETURN NULL; } if (rVariant.vt == VT_BSTR && rVariant.bstrVal) { result = wchar_to_utf8 (rVariant.bstrVal); } else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF)) { LONG uBound, lBound; VARTYPE vt; char *data; SafeArrayGetVartype(rVariant.parray, &vt); if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK || SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK || vt != VT_UI1) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); VariantClear (&rVariant); TRETURN NULL; } result = (char *)xmalloc (uBound - lBound + 1); data = (char *) rVariant.parray->pvData; memcpy (result, data + lBound, uBound - lBound); result[uBound - lBound] = '\0'; } else { log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, property, rVariant.vt); } VariantClear (&rVariant); TRETURN result; } int get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt) { TSTART; VARIANT rVariant; if (get_pa_variant (pDisp, property, &rVariant)) { TRETURN -1; } if (rVariant.vt != VT_I4) { log_debug ("%s:%s: Property `%s' is not a int (vt=%d)", SRCNAME, __func__, property, rVariant.vt); TRETURN -1; } *rInt = rVariant.lVal; VariantClear (&rVariant); TRETURN 0; } /* Helper for exchange address lookup. */ static char * get_recipient_addr_entry_fallbacks_ex (LPDISPATCH addr_entry) { TSTART; /* Maybe check for type here? We are pretty sure that we are exchange */ /* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL is more avilable then the SMTP Address. */ char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL); if (ret) { log_debug ("%s:%s: Found recipient through AB_PROXY: %s", SRCNAME, __func__, anonstr (ret)); char *smtpbegin = strstr(ret, "SMTP:"); if (smtpbegin == ret) { ret += 5; } TRETURN ret; } else { log_debug ("%s:%s: Failed AB_PROXY lookup.", SRCNAME, __func__); } LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser"); if (!ex_user) { log_debug ("%s:%s: Failed to find ExchangeUser", SRCNAME, __func__); TRETURN nullptr; } ret = get_oom_string (ex_user, "PrimarySmtpAddress"); gpgol_release (ex_user); if (ret) { log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s", SRCNAME, __func__, anonstr (ret)); TRETURN ret; } TRETURN nullptr; } /* Helper for additional fallbacks in recipient lookup */ static char * get_recipient_addr_fallbacks (LPDISPATCH recipient) { TSTART; if (!recipient) { TRETURN nullptr; } LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry"); if (!addr_entry) { log_debug ("%s:%s: Failed to find AddressEntry", SRCNAME, __func__); TRETURN nullptr; } char *ret = get_recipient_addr_entry_fallbacks_ex (addr_entry); gpgol_release (addr_entry); TRETURN ret; } /* Try to resolve a recipient group and add it to the recipients vector. Returns true on success. */ static bool try_resolve_group (LPDISPATCH addrEntry, std::vector >&ret, int recipient_type) { TSTART; /* Get the name for debugging */ std::string name; char *cname = get_oom_string (addrEntry, "Name"); if (cname) { name = cname; } xfree (cname); int user_type = get_oom_int (addrEntry, "AddressEntryUserType"); if (user_type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_data ("%s:%s: type of %s is %i", SRCNAME, __func__, anonstr (name.c_str()), user_type); TRETURN false; } LPDISPATCH members = get_oom_object (addrEntry, "Members"); addrEntry = nullptr; if (!members) { TRACEPOINT; TRETURN false; } int count = get_oom_int (members, "Count"); if (!count) { TRACEPOINT; gpgol_release (members); TRETURN false; } bool foundOne = false; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; auto entry = MAKE_SHARED (get_oom_object (members, item_str.c_str())); if (!entry) { TRACEPOINT; continue; } std::string entryName; char *entry_name = get_oom_string (entry.get(), "Name"); if (entry_name) { entryName = entry_name; xfree (entry_name); } int subType = get_oom_int (entry.get(), "AddressEntryUserType"); /* Resolve recursively, yeah fun. */ if (subType == DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_debug ("%s:%s: recursive address entry %s", SRCNAME, __func__, anonstr (entryName.c_str())); if (try_resolve_group (entry.get(), ret, recipient_type)) { foundOne = true; continue; } } std::pair element; element.second = entry; /* Resolve directly ? */ char *addrtype = get_pa_string (entry.get(), PR_ADDRTYPE_DASL); if (addrtype && !strcmp (addrtype, "SMTP")) { xfree (addrtype); char *resolved = get_pa_string (entry.get(), PR_EMAIL_ADDRESS_DASL); if (resolved) { element.first = Recipient (resolved, entryName.c_str (), recipient_type); ret.push_back (element); foundOne = true; continue; } } xfree (addrtype); /* Resolve through Exchange API */ char *ex_resolved = get_recipient_addr_entry_fallbacks_ex (entry.get()); if (ex_resolved) { element.first = Recipient (ex_resolved, entryName.c_str (), recipient_type); ret.push_back (element); foundOne = true; continue; } log_debug ("%s:%s: failed to resolve name %s", SRCNAME, __func__, anonstr (entryName.c_str())); } gpgol_release (members); if (!foundOne) { log_debug ("%s:%s: failed to resolve group %s", SRCNAME, __func__, anonstr (name.c_str())); } TRETURN foundOne; } /* Get the recipient mbox addresses with the addrEntry object corresponding to the resolved address. */ std::vector > get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err) { TSTART; int recipientsCnt = get_oom_int (recipients, "Count"); std::vector > ret; int i; if (!recipientsCnt) { TRETURN ret; } /* Get the recipients */ for (i = 1; i <= recipientsCnt; i++) { char buf[16]; LPDISPATCH recipient; snprintf (buf, sizeof (buf), "Item(%i)", i); recipient = get_oom_object (recipients, buf); if (!recipient) { /* Should be impossible */ log_error ("%s:%s: could not find Item %i;", SRCNAME, __func__, i); if (r_err) { *r_err = true; } break; } int recipient_type = get_oom_int (recipient, "Type"); std::string entryName; char *entry_name = get_oom_string (recipient, "Name"); if (entry_name) { entryName = entry_name; xfree (entry_name); } auto addrEntry = MAKE_SHARED (get_oom_object (recipient, "AddressEntry")); if (addrEntry && try_resolve_group (addrEntry.get (), ret, recipient_type)) { log_debug ("%s:%s: Resolved recipient group", SRCNAME, __func__); gpgol_release (recipient); continue; } std::pair entry; entry.second = addrEntry; char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL); if (resolved) { entry.first = Recipient (resolved, entryName.c_str (), recipient_type); entry.first.setIndex (i); xfree (resolved); gpgol_release (recipient); ret.push_back (entry); continue; } /* No PR_SMTP_ADDRESS first fallback */ resolved = get_recipient_addr_fallbacks (recipient); if (resolved) { entry.first = Recipient (resolved, entryName.c_str (), recipient_type); entry.first.setIndex (i); xfree (resolved); gpgol_release (recipient); ret.push_back (entry); continue; } char *address = get_oom_string (recipient, "Address"); gpgol_release (recipient); log_debug ("%s:%s: Failed to look up Address probably " "EX addr is returned", SRCNAME, __func__); if (address) { entry.first = Recipient (resolved, recipient_type); entry.first.setIndex (i); ret.push_back (entry); xfree (address); } else if (r_err) { *r_err = true; } } TRETURN ret; } /* Gets the resolved smtp addresses of the recpients. */ std::vector get_oom_recipients (LPDISPATCH recipients, bool *r_err) { TSTART; std::vector ret; for (const auto pair: get_oom_recipients_with_addrEntry (recipients, r_err)) { ret.push_back (pair.first); } TRETURN ret; } /* Add an attachment to the outlook dispatcher disp that has an Attachment property. inFile is the path to the attachment. Name is the name that should be used in outlook. */ int add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW, const wchar_t* displayName, std::string &r_error_str, int *r_err_code) { TSTART; LPDISPATCH attachments = get_oom_object (disp, "Attachments"); DISPID dispid; DISPPARAMS dispparams; VARIANT vtResult; VARIANT aVariant[4]; HRESULT hr; BSTR inFileB = nullptr, dispNameB = nullptr; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (attachments, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find attachment dispatcher", SRCNAME, __func__); TRETURN -1; } if (inFileW) { inFileB = SysAllocString (inFileW); } if (displayName) { dispNameB = SysAllocString (displayName); } dispparams.rgvarg = aVariant; /* Contrary to the documentation the Source is the last parameter and not the first. Additionally DisplayName is documented but gets ignored by Outlook since Outlook 2003 */ dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */ dispparams.rgvarg[0].bstrVal = dispNameB; dispparams.rgvarg[1].vt = VT_INT; /* Position */ dispparams.rgvarg[1].intVal = 1; dispparams.rgvarg[2].vt = VT_INT; /* Type */ dispparams.rgvarg[2].intVal = 1; dispparams.rgvarg[3].vt = VT_BSTR; /* Source */ dispparams.rgvarg[3].bstrVal = inFileB; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &vtResult, &execpinfo, &argErr); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x", SRCNAME, __func__, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); if (r_err_code) { *r_err_code = (int) execpinfo.scode; } if (execpinfo.bstrDescription) { char *utf8Err = wchar_to_utf8 (execpinfo.bstrDescription); if (utf8Err) { r_error_str = utf8Err; } xfree (utf8Err); } } if (inFileB) SysFreeString (inFileB); if (dispNameB) SysFreeString (dispNameB); VariantClear (&vtResult); gpgol_release (attachments); TRETURN hr == S_OK ? 0 : -1; } LPDISPATCH get_object_by_id (LPDISPATCH pDisp, REFIID id) { TSTART; LPDISPATCH disp = NULL; if (!pDisp) { TRETURN NULL; } if (gpgol_queryInterface(pDisp, id, (void **)&disp) != S_OK) { TRETURN NULL; } TRETURN disp; } LPDISPATCH get_strong_reference (LPDISPATCH mail) { TSTART; VARIANT var; VariantInit (&var); DISPPARAMS args; VARIANT argvars[2]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); argvars[1].vt = VT_DISPATCH; argvars[1].pdispVal = mail; argvars[0].vt = VT_INT; argvars[0].intVal = 1; args.cArgs = 2; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = NULL; if (!invoke_oom_method_with_parms ( GpgolAddin::get_instance()->get_application(), "GetObjectReference", &var, &args)) { ret = var.pdispVal; log_oom ("%s:%s: Got strong ref %p for %p", SRCNAME, __func__, ret, mail); memdbg_addRef (ret); } else { log_error ("%s:%s: Failed to get strong ref.", SRCNAME, __func__); } VariantClear (&var); TRETURN ret; } LPMESSAGE get_oom_message (LPDISPATCH mailitem) { TSTART; LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject"); if (!mapi_obj) { log_error ("%s:%s: Failed to obtain MAPI Message.", SRCNAME, __func__); TRETURN NULL; } TRETURN (LPMESSAGE) mapi_obj; } static LPMESSAGE get_oom_base_message_from_mapi (LPDISPATCH mapi_message) { TSTART; HRESULT hr; LPDISPATCH secureItem = NULL; LPMESSAGE message = NULL; LPMAPISECUREMESSAGE secureMessage = NULL; secureItem = get_object_by_id (mapi_message, IID_IMAPISecureMessage); if (!secureItem) { log_error ("%s:%s: Failed to obtain SecureItem.", SRCNAME, __func__); TRETURN NULL; } secureMessage = (LPMAPISECUREMESSAGE) secureItem; /* The call to GetBaseMessage is pretty much a jump in the dark. So it would not be surprising to get crashes here in the future. */ log_oom("%s:%s: About to call GetBaseMessage.", SRCNAME, __func__); hr = secureMessage->GetBaseMessage (&message); memdbg_addRef (message); gpgol_release (secureMessage); if (hr != S_OK) { log_error_w32 (hr, "Failed to GetBaseMessage."); TRETURN NULL; } TRETURN message; } LPMESSAGE get_oom_base_message (LPDISPATCH mailitem) { TSTART; LPMESSAGE mapi_message = get_oom_message (mailitem); LPMESSAGE ret = NULL; if (!mapi_message) { log_error ("%s:%s: Failed to obtain mapi_message.", SRCNAME, __func__); TRETURN NULL; } ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message); gpgol_release (mapi_message); TRETURN ret; } static int invoke_oom_method_with_parms_type (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params, int type) { TSTART; HRESULT hr; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { EXCEPINFO execpinfo; init_excepinfo (&execpinfo); DISPPARAMS dispparams = {NULL, NULL, 0, 0}; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, type, params ? params : &dispparams, rVariant, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Method '%s' invokation failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } } TRETURN 0; } int invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params) { TSTART; TRETURN invoke_oom_method_with_parms_type (pDisp, name, rVariant, params, DISPATCH_METHOD); } int invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant) { TSTART; TRETURN invoke_oom_method_with_parms (pDisp, name, rVariant, NULL); } LPMAPISESSION get_oom_mapi_session () { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); LPDISPATCH oom_session = NULL; LPMAPISESSION session = NULL; LPUNKNOWN mapiobj = NULL; HRESULT hr; if (!application) { log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__); TRETURN NULL; } oom_session = get_oom_object (application, "Session"); if (!oom_session) { log_error ("%s:%s: session object not found", SRCNAME, __func__); TRETURN NULL; } mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT"); gpgol_release (oom_session); if (!mapiobj) { log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__); TRETURN NULL; } session = NULL; hr = gpgol_queryInterface (mapiobj, IID_IMAPISession, (void**)&session); gpgol_release (mapiobj); if (hr != S_OK || !session) { log_error ("%s:%s: error getting IMAPISession: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } TRETURN session; } int create_category (LPDISPATCH categories, const char *category, int color) { TSTART; VARIANT cVariant[3]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_name; wchar_t *w_name; unsigned int argErr = 0; init_excepinfo (&execpinfo); if (!categories || !category) { TRACEPOINT; TRETURN 1; } dispid = lookup_oom_dispid (categories, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find Add DISPID", SRCNAME, __func__); TRETURN -1; } /* Do the string dance */ w_name = utf8_to_wchar (category); b_name = SysAllocString (w_name); xfree (w_name); /* Variants are in reverse order ShortcutKey -> 0 / Int Color -> 1 / Int Name -> 2 / Bstr */ VariantInit (&cVariant[2]); cVariant[2].vt = VT_BSTR; cVariant[2].bstrVal = b_name; VariantInit (&cVariant[1]); cVariant[1].vt = VT_INT; cVariant[1].intVal = color; VariantInit (&cVariant[0]); cVariant[0].vt = VT_INT; cVariant[0].intVal = 0; dispparams.cArgs = 3; dispparams.cNamedArgs = 0; dispparams.rgvarg = cVariant; hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); SysFreeString (b_name); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); VariantClear (&cVariant[2]); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (&rVariant); TRETURN -1; } VariantClear (&rVariant); log_oom ("%s:%s: Created category '%s'", SRCNAME, __func__, anonstr (category)); TRETURN 0; } LPDISPATCH get_store_for_id (const char *storeID) { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !storeID) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN nullptr; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } char *id = get_oom_string (store, "StoreID"); if (id && !strcmp (id, storeID)) { gpgol_release (stores); xfree (id); return store; } xfree (id); gpgol_release (store); } gpgol_release (stores); TRETURN nullptr; } void ensure_category_exists (const char *category, int color) { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !category) { TRACEPOINT; TRETURN; } log_oom ("%s:%s: Ensure category exists called for %s, %i", SRCNAME, __func__, category, color); LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } LPDISPATCH categories = get_oom_object (store, "Categories"); gpgol_release (store); if (!categories) { categories = get_oom_object (application, "Session.Categories"); if (!categories) { TRACEPOINT; continue; } } auto count = get_oom_int (categories, "Count"); bool found = false; for (int i = 1; i <= count && !found; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); if (name && !strcmp (category, name)) { log_oom ("%s:%s: Found category '%s'", SRCNAME, __func__, name); found = true; } /* We don't check the color here as the user may change that. */ gpgol_release (category_obj); xfree (name); } if (!found) { if (create_category (categories, category, color)) { log_oom ("%s:%s: Found category '%s'", SRCNAME, __func__, category); } } /* Otherwise we have to create the category */ gpgol_release (categories); } gpgol_release (stores); TRETURN; } int add_category (LPDISPATCH mail, const char *category) { TSTART; char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; TRETURN 1; } if (strstr (tmp, category)) { log_oom ("%s:%s: category '%s' already added.", SRCNAME, __func__, category); TRETURN 0; } std::string newstr (tmp); xfree (tmp); if (!newstr.empty ()) { newstr += CategoryManager::getSeperator () + std::string (" "); } newstr += category; TRETURN put_oom_string (mail, "Categories", newstr.c_str ()); } int remove_category (LPDISPATCH mail, const char *category, bool exactMatch) { TSTART; char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; TRETURN 1; } std::vector categories; std::istringstream f(tmp); std::string s; const std::string sep = CategoryManager::getSeperator(); while (std::getline(f, s, *(sep.c_str()))) { ltrim(s); categories.push_back(s); } xfree (tmp); const std::string categoryStr = category; categories.erase (std::remove_if (categories.begin(), categories.end(), [categoryStr, exactMatch] (const std::string &cat) { if (exactMatch) { return cat == categoryStr; } return cat.compare (0, categoryStr.size(), categoryStr) == 0; }), categories.end ()); std::string newCategories; std::string newsep = sep + " "; join (categories, newsep.c_str (), newCategories); TRETURN put_oom_string (mail, "Categories", newCategories.c_str ()); } static int _delete_category (LPDISPATCH categories, int idx) { TSTART; VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = idx; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; TRETURN invoke_oom_method_with_parms (categories, "Remove", NULL, &dispparams); } int delete_category (LPDISPATCH store, const char *category) { TSTART; if (!store || !category) { TRETURN -1; } LPDISPATCH categories = get_oom_object (store, "Categories"); if (!categories) { categories = get_oom_object ( GpgolAddin::get_instance ()->get_application (), "Session.Categories"); if (!categories) { TRACEPOINT; TRETURN -1; } } auto count = get_oom_int (categories, "Count"); int ret = 0; for (int i = 1; i <= count; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); gpgol_release (category_obj); if (name && !strcmp (category, name)) { if ((ret = _delete_category (categories, i))) { log_error ("%s:%s: Failed to delete category '%s'", SRCNAME, __func__, anonstr (category)); } else { log_debug ("%s:%s: Deleted category '%s'", SRCNAME, __func__, anonstr (category)); } xfree (name); break; } xfree (name); } gpgol_release (categories); TRETURN ret; } void delete_all_categories_starting_with (const char *string) { LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !string) { TRACEPOINT; TRETURN; } log_oom ("%s:%s: Delete categories starting with: \"%s\"", SRCNAME, __func__, string); LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } LPDISPATCH categories = get_oom_object (store, "Categories"); if (!categories) { categories = get_oom_object (application, "Session.Categories"); if (!categories) { TRACEPOINT; gpgol_release (store); continue; } } auto count = get_oom_int (categories, "Count"); std::vector to_delete; for (int i = 1; i <= count; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); if (name && !strncmp (string, name, strlen (string))) { log_oom ("%s:%s: Found category for deletion '%s'", SRCNAME, __func__, anonstr(name)); to_delete.push_back (name); } /* We don't check the color here as the user may change that. */ gpgol_release (category_obj); xfree (name); } /* Do this one after another to avoid messing with indexes. */ for (const auto &str: to_delete) { delete_category (store, str.c_str ()); } gpgol_release (store); /* Otherwise we have to create the category */ gpgol_release (categories); } gpgol_release (stores); TRETURN; } static char * generate_uid () { TSTART; UUID uuid; UuidCreate (&uuid); unsigned char *str; UuidToStringA (&uuid, &str); char *ret = xstrdup ((char*)str); RpcStringFreeA (&str); TRETURN ret; } char * get_unique_id (LPDISPATCH mail, int create, const char *uuid) { TSTART; if (!mail) { TRETURN NULL; } /* Get the User Properties. */ if (!create) { char *uid = get_pa_string (mail, GPGOL_UID_DASL); if (!uid) { log_debug ("%s:%s: No uuid found in oom for '%p'", SRCNAME, __func__, mail); TRETURN NULL; } else { log_debug ("%s:%s: Found uid '%s' for '%p'", SRCNAME, __func__, uid, mail); TRETURN uid; } } char *newuid; if (!uuid) { newuid = generate_uid (); } else { newuid = xstrdup (uuid); } int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid); if (ret) { log_debug ("%s:%s: failed to set uid '%s' for '%p'", SRCNAME, __func__, newuid, mail); xfree (newuid); TRETURN NULL; } log_debug ("%s:%s: '%p' has now the uid: '%s' ", SRCNAME, __func__, mail, newuid); TRETURN newuid; } HWND get_active_hwnd () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveInspector"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveExplorer"); if (!activeWindow) { TRACEPOINT; TRETURN nullptr; } } } /* Both explorer and inspector have this. */ char *caption = get_oom_string (activeWindow, "Caption"); gpgol_release (activeWindow); if (!caption) { TRACEPOINT; TRETURN nullptr; } /* Might not be completly true for multiple explorers on the same folder but good enugh. */ HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32", caption); xfree (caption); TRETURN hwnd; } LPDISPATCH create_mail () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } VARIANT var; VariantInit (&var); VARIANT argvars[1]; DISPPARAMS args; VariantInit (&argvars[0]); argvars[0].vt = VT_I2; argvars[0].intVal = 0; args.cArgs = 1; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = nullptr; if (invoke_oom_method_with_parms (app, "CreateItem", &var, &args)) { log_error ("%s:%s: Failed to create mailitem.", SRCNAME, __func__); TRETURN ret; } ret = var.pdispVal; TRETURN ret; } LPDISPATCH get_account_for_mail (const char *mbox) { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH accounts = get_oom_object (app, "Session.Accounts"); if (!accounts) { TRACEPOINT; TRETURN nullptr; } int count = get_oom_int (accounts, "Count"); for (int i = 1; i <= count; i++) { std::string item = std::string ("Item(") + std::to_string (i) + ")"; LPDISPATCH account = get_oom_object (accounts, item.c_str ()); if (!account) { TRACEPOINT; continue; } char *smtpAddr = get_oom_string (account, "SmtpAddress"); if (!smtpAddr) { gpgol_release (account); TRACEPOINT; continue; } if (!stricmp (mbox, smtpAddr)) { gpgol_release (accounts); xfree (smtpAddr); TRETURN account; } gpgol_release (account); xfree (smtpAddr); } gpgol_release (accounts); log_error ("%s:%s: Failed to find account for '%s'.", SRCNAME, __func__, anonstr (mbox)); TRETURN nullptr; } char * get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount"); if (!sender) { TRETURN nullptr; } char *buf = get_oom_string (sender, "SmtpAddress"); char *dispName = get_oom_string (sender, "DisplayName"); gpgol_release (sender); /* Check for G Suite account */ if (dispName && !strcmp ("G Suite", dispName) && r_is_GSuite) { *r_is_GSuite = true; } xfree (dispName); if (buf && strlen (buf)) { log_debug ("%s:%s: found sender", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_Sender (LPDISPATCH mailitem) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "Sender"); if (!sender) { TRETURN nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__); TRETURN buf; } xfree (buf); /* We have a sender object but not yet an smtp address likely exchange. Try some more propertys of the message. */ buf = get_pa_string (mailitem, PR_TAG_SENDER_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__); TRETURN buf; } xfree (buf); buf = get_pa_string (mailitem, PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 4", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_CurrentUser (LPDISPATCH mailitem) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "Session.CurrentUser"); if (!sender) { TRETURN nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 5", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_SenderEMailAddress (LPDISPATCH mailitem) { TSTART; char *type = get_oom_string (mailitem, "SenderEmailType"); if (type && !strcmp ("SMTP", type)) { char *senderMail = get_oom_string (mailitem, "SenderEmailAddress"); if (senderMail) { log_debug ("%s:%s: Sender found", SRCNAME, __func__); xfree (type); TRETURN senderMail; } } xfree (type); TRETURN nullptr; } char * get_sender_SentRepresentingAddress (LPDISPATCH mailitem) { TSTART; char *buf = get_pa_string (mailitem, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W_DASL); if (buf && strlen (buf)) { log_debug ("%s:%s Found sent representing address \"%s\"", SRCNAME, __func__, anonstr (buf)); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_inline_body () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { TRETURN nullptr; } char *body = get_oom_string (inlineResponse, "Body"); gpgol_release (inlineResponse); TRETURN body; } int get_ex_major_version_for_addr (const char *mbox) { TSTART; LPDISPATCH account = get_account_for_mail (mbox); if (!account) { TRACEPOINT; TRETURN -1; } char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion"); gpgol_release (account); if (!version_str) { TRETURN -1; } log_debug ("%s:%s: Detected exchange major version: %s", SRCNAME, __func__, version_str); long int version = strtol (version_str, nullptr, 10); xfree (version_str); TRETURN (int) version; } int get_ol_ui_language () { TSTART; LPDISPATCH app = GpgolAddin::get_instance()->get_application(); if (!app) { TRACEPOINT; TRETURN 0; } LPDISPATCH langSettings = get_oom_object (app, "LanguageSettings"); if (!langSettings) { TRACEPOINT; TRETURN 0; } VARIANT var; VariantInit (&var); VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 2; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int ret = invoke_oom_method_with_parms_type (langSettings, "LanguageID", &var, &dispparams, DISPATCH_PROPERTYGET); gpgol_release (langSettings); if (ret) { TRACEPOINT; TRETURN 0; } if (var.vt != VT_INT && var.vt != VT_I4) { TRACEPOINT; TRETURN 0; } int result = var.intVal; VariantClear (&var); TRETURN result; } void log_addins () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN; } LPDISPATCH addins = get_oom_object (app, "COMAddins"); if (!addins) { TRACEPOINT; TRETURN; } std::string activeAddins; int count = get_oom_int (addins, "Count"); for (int i = 1; i <= count; i++) { VARIANT aVariant[1]; VARIANT rVariant; VariantInit (&rVariant); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = i; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; /* We need this instead of get_oom_object item(1) as usual becase the item method accepts a string or an int. String would be the ProgID and int is just the index. So Fun. */ if (invoke_oom_method_with_parms_type (addins, "Item", &rVariant, &dispparams, DISPATCH_METHOD | DISPATCH_PROPERTYGET)) { log_error ("%s:%s: Failed to invoke item func.", SRCNAME, __func__); continue; } if (rVariant.vt != (VT_DISPATCH)) { log_error ("%s:%s: Invalid ret val", SRCNAME, __func__); continue; } LPDISPATCH addin = rVariant.pdispVal; if (!addin) { TRACEPOINT; continue; } memdbg_addRef (addin); bool connected = get_oom_bool (addin, "Connect"); if (!connected) { gpgol_release (addin); continue; } char *progId = get_oom_string (addin, "ProgId"); gpgol_release (addin); if (!progId) { TRACEPOINT; continue; } activeAddins += std::string (progId) + "\n"; xfree (progId); } gpgol_release (addins); log_debug ("%s:%s:Active Addins:\n%s", SRCNAME, __func__, activeAddins.c_str ()); TRETURN; } bool is_preview_pane_visible (LPDISPATCH explorer) { TSTART; if (!explorer) { TRACEPOINT; TRETURN false; } VARIANT var; VariantInit (&var); VARIANT argvars[1]; DISPPARAMS args; VariantInit (&argvars[0]); argvars[0].vt = VT_INT; argvars[0].intVal = 3; args.cArgs = 1; args.cNamedArgs = 0; args.rgvarg = argvars; if (invoke_oom_method_with_parms (explorer, "IsPaneVisible", &var, &args)) { log_error ("%s:%s: Failed to check visibilty.", SRCNAME, __func__); TRETURN false; } if (var.vt != VT_BOOL) { TRACEPOINT; TRETURN false; } TRETURN !!var.boolVal; } static LPDISPATCH add_user_prop (LPDISPATCH user_props, const char *name) { TSTART; if (!user_props || !name) { TRACEPOINT; TRETURN nullptr; } wchar_t *w_name = utf8_to_wchar (name); BSTR b_name = SysAllocString (w_name); xfree (w_name); /* Args: 0: DisplayFormat int OlUserPropertyType 1: AddToFolderFields Bool Should the filed be added to the folder. 2: Type int OlUserPropertyType Type of the field. 3: Name Bstr Name of the field. Returns the added Property. */ VARIANT var; VariantInit (&var); DISPPARAMS args; VARIANT argvars[4]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); VariantInit (&argvars[2]); VariantInit (&argvars[3]); argvars[0].vt = VT_INT; argvars[0].intVal = 1; // 1 means text. argvars[1].vt = VT_BOOL; argvars[1].boolVal = VARIANT_FALSE; argvars[2].vt = VT_INT; argvars[2].intVal = 1; argvars[3].vt = VT_BSTR; argvars[3].bstrVal = b_name; args.cArgs = 4; args.cNamedArgs = 0; args.rgvarg = argvars; int res = invoke_oom_method_with_parms (user_props, "Add", &var, &args); VariantClear (&argvars[0]); VariantClear (&argvars[1]); VariantClear (&argvars[2]); VariantClear (&argvars[3]); if (res) { log_oom ("%s:%s: Failed to add property %s.", SRCNAME, __func__, name); TRETURN nullptr; } if (var.vt != VT_DISPATCH) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH ret = var.pdispVal; memdbg_addRef (ret); TRETURN ret; } LPDISPATCH find_user_prop (LPDISPATCH user_props, const char *name) { TSTART; if (!user_props || !name) { TRACEPOINT; TRETURN nullptr; } VARIANT var; VariantInit (&var); wchar_t *w_name = utf8_to_wchar (name); BSTR b_name = SysAllocString (w_name); xfree (w_name); /* Name -> 1 / Bstr Custom 0 -> Bool True for search in custom properties. False for builtin properties. */ DISPPARAMS args; VARIANT argvars[2]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); argvars[1].vt = VT_BSTR; argvars[1].bstrVal = b_name; argvars[0].vt = VT_BOOL; argvars[0].boolVal = VARIANT_TRUE; args.cArgs = 2; args.cNamedArgs = 0; args.rgvarg = argvars; int res = invoke_oom_method_with_parms (user_props, "Find", &var, &args); VariantClear (&argvars[0]); VariantClear (&argvars[1]); if (res) { log_oom ("%s:%s: Failed to find property %s.", SRCNAME, __func__, name); TRETURN nullptr; } if (var.vt != VT_DISPATCH) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH ret = var.pdispVal; memdbg_addRef (ret); TRETURN ret; } LPDISPATCH find_or_add_text_prop (LPDISPATCH user_props, const char *name) { TSTART; LPDISPATCH ret = find_user_prop (user_props, name); if (ret) { TRETURN ret; } ret = add_user_prop (user_props, name); TRETURN ret; } void release_disp (LPDISPATCH obj) { TSTART; gpgol_release (obj); TRETURN; } enum FolderID { olFolderCalendar = 9, olFolderConflicts = 19, olFolderContacts = 10, olFolderDeletedItems = 3, olFolderDrafts = 16, olFolderInbox = 6, olFolderJournal = 11, olFolderJunk = 23, olFolderLocalFailures = 21, olFolderManagedEmail = 29, olFolderNotes = 12, olFolderOutbox = 4, olFolderSentMail = 5, olFolderServerFailures = 22, olFolderSuggestedContacts = 30, olFolderSyncIssues = 20, olFolderTasks = 13, olFolderToDo = 28, olPublicFoldersAllPublicFolders = 18, olFolderRssFeeds = 25, }; static bool is_mail_in_folder (LPDISPATCH mailitem, int folder) { TSTART; if (!mailitem) { STRANGEPOINT; TRETURN false; } auto store = MAKE_SHARED (get_oom_object (mailitem, "Parent.Store")); if (!store) { log_debug ("%s:%s: Mail has no parent folder. Probably unsafed", SRCNAME, __func__); TRETURN false; } std::string tmp = std::string("GetDefaultFolder(") + std::to_string (folder) + std::string(")"); auto target_folder = MAKE_SHARED (get_oom_object (store.get(), tmp.c_str())); if (!target_folder) { STRANGEPOINT; TRETURN false; } auto mail_folder = MAKE_SHARED (get_oom_object (mailitem, "Parent")); if (!mail_folder) { STRANGEPOINT; TRETURN false; } char *target_id = get_oom_string (target_folder.get(), "entryID"); if (!target_id) { STRANGEPOINT; TRETURN false; } char *folder_id = get_oom_string (mail_folder.get(), "entryID"); if (!folder_id) { STRANGEPOINT; free (target_id); TRETURN false; } bool ret = !strcmp (target_id, folder_id); free (target_id); free (folder_id); TRETURN ret; } bool is_junk_mail (LPDISPATCH mailitem) { TSTART; TRETURN is_mail_in_folder (mailitem, FolderID::olFolderJunk); } bool is_draft_mail (LPDISPATCH mailitem) { TSTART; TRETURN is_mail_in_folder (mailitem, FolderID::olFolderDrafts); } void format_variant (std::stringstream &stream, VARIANT* var) { if (!var) { stream << " (null) "; } stream << "VT: " << std::hex << var->vt << " Value: "; VARTYPE vt = var->vt; if (vt == VT_BOOL) { stream << (var->boolVal == VARIANT_FALSE ? "false" : "true"); } else if (vt == (VT_BOOL | VT_BYREF)) { stream << (*(var->pboolVal) == VARIANT_FALSE ? "false" : "true"); } else if (vt == VT_BSTR) { char *buf = wchar_to_utf8 (var->bstrVal); stream << "BStr: " << buf; xfree (buf); } else if (vt == VT_INT || vt == VT_I4) { stream << var->intVal; } else if (vt == VT_DISPATCH) { char *buf = get_object_name ((LPUNKNOWN) var->pdispVal); stream << "IDispatch: " << buf; xfree (buf); } else if (vt == (VT_VARIANT | VT_BYREF)) { format_variant (stream, var->pvarVal); } else { stream << "?"; } stream << std::endl; } std::string format_dispparams (DISPPARAMS *p) { if (!p) { return "(null)"; } std::stringstream stream; stream << "Count: " << p->cArgs << " CNamed: " << p->cNamedArgs << std::endl; for (int i = 0; i < p->cArgs; i++) { format_variant (stream, p->rgvarg + i); } return stream.str (); } int count_visible_attachments (LPDISPATCH attachments) { int ret = 0; if (!attachments) { return 0; } int att_count = get_oom_int (attachments, "Count"); for (int i = 1; i <= att_count; i++) { std::string item_str; item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ()); if (!oom_attach) { log_error ("%s:%s: Failed to get attachment.", SRCNAME, __func__); continue; } VARIANT var; VariantInit (&var); if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var)) { /* SECURITY: Testing has shown that for all mail types GpgOL handles that might contain an unsigned attachment we always have the MAPIOBJECT / get the hidden state. Only the transient MIME attachments. The ones used for the MAPI to MIME conversion and which are hidden by GpgOL and handled by GpgOL will have no MAPIOBJECT when a mail is opened from file. So this will remove the warning that "smime.p7m" or "gpgol_mime_structure.txt" are unsigned and unencrypted attachments. */ log_dbg ("Failed to get hidden state."); LPUNKNOWN mapiobj = get_oom_iunknown (oom_attach, "MAPIOBJECT"); if (!mapiobj) { const auto dispName = get_oom_string_s (oom_attach, "DisplayName"); log_dbg ("Attachment: %s has no mapiobject. Ignoring it.", anonstr (dispName.c_str ())); } else { gpgol_release (mapiobj); const auto dispName = get_oom_string_s (oom_attach, "DisplayName"); log_dbg ("Attachment %s without hidden state but mapiobj. " "Count as visible.", anonstr (dispName.c_str ())); ret++; } } else if (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE) { ret++; } gpgol_release (oom_attach); VariantClear (&var); } return ret; } int invoke_oom_method_with_int (LPDISPATCH pDisp, const char *name, int arg, VARIANT *rVariant) { TSTART; DISPPARAMS parms; VARIANT argvars[1]; VariantInit (&argvars[0]); argvars[0].vt = VT_INT; argvars[0].intVal = arg; parms.cArgs = 1; parms.cNamedArgs = 0; parms.rgvarg = argvars; TRETURN invoke_oom_method_with_parms (pDisp, name, rVariant, &parms); } int invoke_oom_method_with_string (LPDISPATCH pDisp, const char *name, const char *arg, VARIANT *rVariant) { TSTART; if (!arg) { TRETURN 0; } wchar_t *warg = utf8_to_wchar (arg); if (!warg) { TRETURN 1; } VARIANT aVariant[1]; VariantInit (&aVariant[0]); aVariant[0].vt = VT_BSTR; aVariant[0].bstrVal = SysAllocString (warg); xfree (warg); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int ret = invoke_oom_method_with_parms (pDisp, name, rVariant, &dispparams); VariantClear(&aVariant[0]); TRETURN ret; } int set_oom_recipients (LPDISPATCH item, const std::vector &recps) { if (!item) { STRANGEPOINT; TRETURN -1; } auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients")); if (!oom_recps) { STRANGEPOINT; TRETURN -1; } /* First clear out the current recipients. */ int ret = invoke_oom_method (oom_recps.get (), "RemoveAll", nullptr); if (ret) { STRANGEPOINT; TRETURN ret; } for (const auto &recp: recps) { if (recp.type() == Recipient::olOriginator) { /* Skip the originator, we only add it internally but it does not need to be in OOM. */ continue; } VARIANT result; VariantInit (&result); ret = invoke_oom_method_with_string (oom_recps.get (), "Add", recp.mbox ().c_str (), &result); if (ret) { log_err ("Failed to add recipient."); TRETURN ret; } if (result.vt != VT_DISPATCH || !result.pdispVal) { log_err ("No recipient result."); continue; } if (put_oom_int (result.pdispVal, "Type", recp.type())) { log_err ("Failed to set recipient type."); } /* This releases the recipient. */ VariantClear (&result); } TRETURN 0; } int remove_oom_recipient (LPDISPATCH item, const std::string &mbox) { TSTART; if (!item) { STRANGEPOINT; TRETURN -1; } auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients")); if (!oom_recps) { STRANGEPOINT; TRETURN -1; } bool r_err = false; const auto recps = get_oom_recipients (oom_recps.get (), &r_err); if (r_err) { log_debug ("Failure to lookup recipients via OOM"); TRETURN -1; } for (const auto &recp: recps) { if (recp.mbox () == mbox && recp.index () != -1) { TRETURN invoke_oom_method_with_int (oom_recps.get (), "Remove", recp.index (), nullptr); } } TRETURN -1; } void oom_dump_idispatch (LPDISPATCH obj) { log_dbg ("Start infos about %p", obj); if (!obj) { log_dbg ("It's NULL"); return; } log_dbg ("Name: '%s'", get_object_name_s (obj).c_str ()); LPTYPEINFO typeinfo = nullptr; HRESULT hr = obj->GetTypeInfo (0, 0, &typeinfo); if (!typeinfo || FAILED (hr)) { log_dbg ("No typeinfo."); return; } TYPEATTR* pta = NULL; hr = typeinfo->GetTypeAttr(&pta); if (!pta || FAILED (hr)) { log_dbg ("No type attr"); return; } /* First the IID to have it clear */ LPOLESTR lpsz = NULL; hr = StringFromIID(pta->guid, &lpsz); if(FAILED (hr)) { hr = StringFromCLSID (pta->guid, &lpsz); } if(SUCCEEDED (hr)) { log_dbg ("Interface: %S", lpsz); CoTaskMemFree(lpsz); } FUNCDESC *pfd = nullptr; /* Lets see what functions we have. */ for(int i = 0; i < pta->cFuncs; i++) { typeinfo->GetFuncDesc(i, &pfd); BSTR names[1]; unsigned int dumb; typeinfo->GetNames(pfd->memid, names, 1, &dumb); if (!names[0]) { typeinfo->ReleaseFuncDesc(pfd); continue; } log_dbg ("%i: %S id=0x%li With %d param(s)\n", i, (names[0]), pfd->memid, pfd->cParams); typeinfo->ReleaseFuncDesc(pfd); SysFreeString(names[0]); } typeinfo->ReleaseTypeAttr(pta); /* Now for some interesting object relations that many oom objecs have. */ const char * relations[] = { "Parent", "GetInspector", "Session", "Sender", nullptr }; for (int i = 0; relations [i]; i++) { LPDISPATCH rel = get_oom_object (obj, relations[i]); log_dbg ("%s: %s", relations [i], get_object_name_s (rel).c_str ()); gpgol_release (rel); } /* Now for some interesting string values. */ const char * stringVals[] = { "EntryID", "Subject", "MessageClass", "Body", nullptr }; for (int i = 0; stringVals[i]; i++) { const auto str = get_oom_string_s (obj, stringVals[i]); log_dbg ("%s: %s", stringVals[i], str.c_str ()); } log_dbg ("Object dump done"); return; } int get_oom_crypto_flags (LPDISPATCH mailitem) { TSTART; int r_val = 0; int err = get_pa_int (mailitem, PR_SECURITY_FLAGS_DASL, &r_val); if (err) { log_dbg ("Failed to get security flags."); TRETURN 0; } TRETURN r_val; } diff --git a/src/oomhelp.h b/src/oomhelp.h index 7fe34d7..353ff63 100644 --- a/src/oomhelp.h +++ b/src/oomhelp.h @@ -1,499 +1,499 @@ /* oomhelp.h - Defs for helper functions for the Outlook Object Model * Copyright (C) 2009 g10 Code GmbH * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. - * + * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef OOMHELP_H #define OOMHELP_H #include #include "mymapi.h" #include "common.h" #include #include #include #define MSOCONTROLBUTTON 1 #define MSOCONTROLEDIT 2 #define MSOCONTROLDROPDOWN 3 #define MSOCONTROLCOMBOBOX 4 #define MSOCONTROLPOPUP 10 class Recipient; -enum +enum { msoButtonAutomatic = 0, msoButtonIcon = 1, msoButtonCaption = 2, msoButtonIconAndCaption = 3, msoButtonIconAndWrapCaption = 7, msoButtonIconAndCaptionBelow = 11, msoButtonWrapCaption = 14, - msoButtonIconAndWrapCaptionBelow = 15 + msoButtonIconAndWrapCaptionBelow = 15 }; enum { msoButtonDown = -1, msoButtonUp = 0, msoButtonMixed = 2 }; DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); -DEFINE_GUID(IID_IConnectionPoint, +DEFINE_GUID(IID_IConnectionPoint, 0xb196b286, 0xbab4, 0x101a, 0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07); -DEFINE_GUID(IID_IConnectionPointContainer, +DEFINE_GUID(IID_IConnectionPointContainer, 0xb196b284, 0xbab4, 0x101a, 0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07); DEFINE_GUID(IID_IPictureDisp, 0x7bf80981, 0xbf32, 0x101a, 0x8b, 0xbb, 0x00, 0xaa, 0x00, 0x30, 0x0c, 0xab); DEFINE_GUID(IID_FolderEvents, 0x000630F7, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ApplicationEvents, 0x0006304E, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ApplicationEvents_11, 0x0006302C, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ExplorerEvents, 0x0006300F, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ExplorersEvents, 0x00063078, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_MailItemEvents, 0x0006302B, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_MailItem, 0x00063034, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_IMAPISecureMessage, 0x253cc320, 0xeab6, 0x11d0, 0x82, 0x22, 0, 0x60, 0x97, 0x93, 0x87, 0xea); DEFINE_OLEGUID(IID_IUnknown, 0x00000000, 0, 0); DEFINE_OLEGUID(IID_IDispatch, 0x00020400, 0, 0); DEFINE_OLEGUID(IID_IOleWindow, 0x00000114, 0, 0); #ifndef PR_SMTP_ADDRESS_DASL #define PR_SMTP_ADDRESS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x39FE001E" #endif #ifndef PR_EMS_AB_PROXY_ADDRESSES_DASL #define PR_EMS_AB_PROXY_ADDRESSES_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x800F101E" #endif #ifndef PR_ATTACHMENT_HIDDEN_DASL #define PR_ATTACHMENT_HIDDEN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B" #endif #ifndef PR_ADDRTYPE_DASL #define PR_ADDRTYPE_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3002001E" #endif #ifndef PR_EMAIL_ADDRESS_DASL #define PR_EMAIL_ADDRESS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3003001E" #endif #define PR_MESSAGE_CLASS_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x001A001F" #define GPGOL_ATTACHTYPE_DASL \ "http://schemas.microsoft.com/mapi/string/" \ "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL Attach Type/0x00000003" #define GPGOL_UID_DASL \ "http://schemas.microsoft.com/mapi/string/" \ "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F" #define PR_ATTACH_DATA_BIN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x37010102" #define PR_BODY_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x1000001F" #define PR_ATTACHMENT_HIDDEN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B" #define PR_ATTACH_MIME_TAG_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x370E001F" #define PR_ATTACH_CONTENT_ID_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3712001F" #define PR_ATTACH_FLAGS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x37140003" #define PR_TAG_SENDER_SMTP_ADDRESS \ "http://schemas.microsoft.com/mapi/proptag/0x5D01001F" #define PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS \ "http://schemas.microsoft.com/mapi/proptag/0x5D08001F" #define PR_PIDNameContentType_DASL \ "http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/content-type/0x0000001F" #define PR_BLOCK_STATUS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x10960003" #define PR_SENT_REPRESENTING_EMAIL_ADDRESS_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x0065001F" #define PR_SENDER_NAME_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x0C1A001F" #define PR_SENT_REPRESENTING_NAME_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x0042001F" #define PR_SECURITY_FLAGS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x6E010003" #define DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE 11 #define DISPID_DIRTY_RAT 0xF024 typedef std::shared_ptr shared_disp_t; /* Function to contain the gpgol_release macro */ void release_disp (LPDISPATCH obj); #define MAKE_SHARED(X) shared_disp_t ((LPDISPATCH)X, &release_disp) /* Return the malloced name of an COM+ object. */ char *get_object_name (LPUNKNOWN obj); std::string get_object_name_s (LPUNKNOWN obj); std::string get_object_name_s (shared_disp_t obj); /* Helper to lookup a dispid. */ DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name); /* Return the OOM object's IDispatch interface described by FULLNAME. */ LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname); /* Do the same but with a shared disp return value */ shared_disp_t get_oom_object_s (LPDISPATCH pStart, const char *fullname); shared_disp_t get_oom_object_s (shared_disp_t pStart, const char *fullname); /* Set the Icon of a CommandBarControl. */ int put_oom_icon (LPDISPATCH pDisp, int rsource_id, int size); /* Set the boolean property NAME to VALUE. */ int put_oom_bool (LPDISPATCH pDisp, const char *name, int value); /* Set the property NAME to VALUE. */ int put_oom_int (LPDISPATCH pDisp, const char *name, int value); /* Set the property NAME to STRING. */ int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string); /* Set the property NAME to DISP. */ int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH value); /* Set the byte array property NAME to VALUE. */ int put_oom_array (LPDISPATCH pDisp, const char *name, unsigned char *value, size_t size); /* Get the boolean property NAME of the object PDISP. */ int get_oom_bool (LPDISPATCH pDisp, const char *name); /* Get the integer property NAME of the object PDISP. */ int get_oom_int (LPDISPATCH pDisp, const char *name); int get_oom_int (shared_disp_t pDisp, const char *name); /* Get the string property NAME of the object PDISP. */ char *get_oom_string (LPDISPATCH pDisp, const char *name); std::string get_oom_string_s (LPDISPATCH pDisp, const char *name); std::string get_oom_string_s (shared_disp_t pDisp, const char *name); /* Get an IUnknown object from property NAME of PDISP. */ LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name); /* Return the control object with tag property value TAG. */ LPDISPATCH get_oom_control_bytag (LPDISPATCH pObj, const char *tag); /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ LPDISPATCH add_oom_button (LPDISPATCH pObj); /* Delete a button. */ void del_oom_button (LPDISPATCH button); /* Get the HWND of the active window in the current context */ HWND get_oom_context_window (LPDISPATCH context); /* Get the address of the recipients as string list. The second part of the pair returned is the recipient type which corresponds in value to Mail::recipientType. If r_err is not null it is set to true in case of an error. */ std::vector get_oom_recipients (LPDISPATCH recipients, bool *r_err = nullptr); /* Same as above but include the AddrEntry object in the result. Caller needs to release the AddrEntry. */ std::vector > get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err = nullptr); /* Add an attachment to a dispatcher */ int add_oom_attachment (LPDISPATCH disp, const wchar_t* inFile, const wchar_t *displayName, std::string &r_err_str, int *r_err_code); /* Look up a string with the propertyAccessor interface */ char * get_pa_string (LPDISPATCH pDisp, const char *property); /* Look up a long with the propertyAccessor interface. returns -1 on error.*/ int get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt); /* Set a variant with the propertyAccessor interface. This is tested to work at least vor BSTR variants. Trying to set PR_ATTACH_DATA_BIN_DASL with this failed with hresults 0x80020005 type mismatch or 0x80020008 vad variable type for: VT_ARRAY | VT_UI1 | VT_BYREF VT_SAFEARRAY | VT_UI1 | VT_BYREF VT_BSTR | VT_BYREF VT_BSTR VT_ARRAY | VT_UI1 VT_SAFEARRAY | VT_UI1 No idea whats wrong there. Needs more experiments. The Type is only documented as "Binary". Outlookspy also fails with the same error when trying to modify the property. */ int put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value); int put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value); int put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value); /* Look up a variant with the propertyAccessor interface */ int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant); /* Look up a LONG with the propertyAccessor interface */ LONG get_pa_long (LPDISPATCH pDisp, const char *dasl_id); /* Queries the interface of the dispatcher for the id id. Returns NULL on error. The returned Object must be released. Mainly useful to check if an object is what it appears to be. */ LPDISPATCH get_object_by_id (LPDISPATCH pDisp, REFIID id); /* Obtain the MAPI Message corresponding to the Mailitem. Returns NULL on error. The returned Message needs to be released by the caller */ LPMESSAGE get_oom_message (LPDISPATCH mailitem); /* Obtain the Base MAPI Message of a MailItem. The parameter should be a pointer to a MailItem. returns NULL on error. The returned Message needs to be released by the caller. */ LPMESSAGE get_oom_base_message (LPDISPATCH mailitem); /* Get a strong reference for a mail object by calling Application.GetObjectReference with type strong. The documentation is unclear what this acutally does. This function is left over from experiments about strong references. Maybe there is a use for them. The reference we use in the Mail object is documented as a Weak reference. But changing that does not appear to make a difference. */ LPDISPATCH get_strong_reference (LPDISPATCH mail); /* Invoke a method of an outlook object. returns true on success false otherwise. rVariant should either point to a propery initialized variant (initinalized wiht VariantInit) to hold the return value or a pointer to NULL. */ int invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant); /* Invoke a method of an outlook object. returns true on success false otherwise. rVariant should either point to a propery initialized variant (initinalized wiht VariantInit) to hold the return value or a pointer to NULL. parms can optionally be used to provide a DISPPARAMS structure with parameters for the function. */ int invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params); /* Same as invoke oom method but do the string marshalling for arg */ int invoke_oom_method_with_string (LPDISPATCH pDisp, const char *name, const char *arg, VARIANT *rVariant = nullptr); /* Same as invoke oom method but with a single int argument */ int invoke_oom_method_with_int (LPDISPATCH pDisp, const char *name, int arg, VARIANT *rVariant = nullptr); /* Try to obtain the mapisession through the Application. returns NULL on error.*/ LPMAPISESSION get_oom_mapi_session (void); /* Ensure a category of the name name exists. Creates the category with the specified color if required. returns 0 on success. */ void ensure_category_exists (const char *category, int color); /* Add a category to a mail if it is not already added. */ int add_category (LPDISPATCH mail, const char *category); /* Remove a category from a mail if it was added. */ int remove_category (LPDISPATCH mail, const char *category, bool exactMatch); /* Create the category */ int create_category (LPDISPATCH categories, const char *category, int color); /* Delete a category from the store. */ int delete_category (LPDISPATCH store, const char *category); /* Delete categories starting with "string" from all stores. */ void delete_all_categories_starting_with (const char *string); /* Iterate over application stores and return the one with the ID @storeID */ LPDISPATCH get_store_for_id (const char *storeID); /* Get a unique identifier for a mail object. The uuid is a custom property. If create is set a new uuid will be added if none exists and the value of that uuid returned. The optinal uuid value can be set to be used as uuid instead of a generated one. Return value has to be freed by the caller. */ char * get_unique_id (LPDISPATCH mail, int create, const char* uuid); /* Uses the Application->ActiveWindow to determine the hwnd through FindWindow and the caption. Does not use IOleWindow because that was unreliable somhow. */ HWND get_active_hwnd (void); /* Create a new mailitem and return it */ LPDISPATCH create_mail (void); LPDISPATCH get_account_for_mail (const char *mbox); /* Print all active addins to log */ void log_addins (void); /* Sender fallbacks. All return either null or a malloced address. */ char *get_sender_CurrentUser (LPDISPATCH mailitem); char *get_sender_Sender (LPDISPATCH mailitem); char *get_sender_SenderEMailAddress (LPDISPATCH mailitem); /* Get the body of the active inline response */ char *get_inline_body (void); /* Get the major version of the exchange server of the account for the mail address "mbox". Returns -1 if no version could be detected or exchange is not used.*/ int get_ex_major_version_for_addr (const char *mbox); /* Get the language code used for Outlooks UI */ int get_ol_ui_language (void); char *get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite); /* Get the SentRepresentingAddress */ char *get_sender_SentRepresentingAddress (LPDISPATCH mailitem); /* memtracing query interface */ HRESULT gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj); HRESULT gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN FAR * lppUnk); /* Check if the preview pane in the explorer is visible */ bool is_preview_pane_visible (LPDISPATCH explorer); /* Find or add a text user property with that name. */ LPDISPATCH find_or_add_text_prop (LPDISPATCH props, const char *name); /* Find a user property and return it if found. */ LPDISPATCH find_user_prop (LPDISPATCH props, const char *name); /* Return true if this message is in the junk folder for this account */ bool is_junk_mail (LPDISPATCH mailitem); /* Return true if this message is in the draft folder for this account */ bool is_draft_mail (LPDISPATCH mailitem); /* Returns info about a dispparms variable for debugging. */ void format_variant (std::istringstream &stream, VARIANT *var); std::string format_dispparams (DISPPARAMS *p); /* Returns the count of attachments that are not hidden. */ int count_visible_attachments (LPDISPATCH attachments); /* Remove a recipient from the OOM. */ int remove_oom_recipient (LPDISPATCH item, const std::string &mbox); /* Remove all recipients and replace them with the list of our objects. */ int set_oom_recipients (LPDISPATCH item, const std::vector &recps); /* Print some introspective infos about the object. */ void oom_dump_idispatch (LPDISPATCH obj); /* Get the hidden dirty property of the object. */ int get_oom_dirty (LPDISPATCH pDisp); /* Get the Outlook crypto flags indicating in Outlook if the mail should be encrypted with S/MIME. */ int get_oom_crypto_flags (LPDISPATCH mailitem); /* Setter for dirty - Returns an error that the property is write protected on mails. But maybe we can use it somewhere else. Code is ifdeffed out. int put_oom_dirty (LPDISPATCH pDisp, bool val); */ #endif /*OOMHELP_H*/ diff --git a/src/revert.cpp b/src/revert.cpp index eceef6f..34bd6d0 100644 --- a/src/revert.cpp +++ b/src/revert.cpp @@ -1,408 +1,408 @@ /* revert.cpp - Convert messages back to the orignal format * Copyright (C) 2008 g10 Code GmbH * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH - * + * * This file is part of GpgOL. - * + * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include "mymapi.h" #include "mymapitags.h" #include "common.h" #include "oomhelp.h" #include "mapihelp.h" #include "mimemaker.h" #include "mail.h" /* Helper method for mailitem_revert to add changes on the mapi side and save them. */ static int finalize_mapi (LPMESSAGE message) { HRESULT hr; SPropTagArray proparray; ULONG tag_id; if (get_gpgollastdecrypted_tag (message, &tag_id)) { log_error ("%s:%s: can't getlastdecrypted tag", SRCNAME, __func__); return -1; } proparray.cValues = 1; proparray.aulPropTag[0] = tag_id; hr = message->DeleteProps (&proparray, NULL); if (hr) { log_error ("%s:%s: failed to delete lastdecrypted tag", SRCNAME, __func__); return -1; } return mapi_save_changes (message, FORCE_SAVE); } /* Similar to gpgol_message_revert but works on OOM and is used by the Ol > 2010 implementation. Doing this through OOM was necessary as the MAPI structures in the write event are not in sync with the OOM side. Trying to revert in the AfterWrite where MAPI is synced led to an additional save_changes after the wipe and so an additional sync. Updating the BODY through MAPI did not appear to work at all. Not sure why this is the case. Using the property accessor methods instead of MAPI properties might also not be necessary. Returns 0 on success, -1 on error. On error this function might leave plaintext in the mail. */ EXTERN_C LONG __stdcall gpgol_mailitem_revert (LPDISPATCH mailitem) { LPDISPATCH attachments = NULL; LPMESSAGE message = NULL; char *item_str; char *msgcls = NULL; int i; int count = 0; LONG result = -1; msgtype_t msgtype; int body_restored = 0; LPDISPATCH *to_delete = NULL; int del_cnt = 0; LPDISPATCH to_restore = NULL; int mosstmpl_found = 0; int is_smime = 0; Mail *mail = NULL; /* Check whether we need to care about this message. */ msgcls = get_pa_string (mailitem, PR_MESSAGE_CLASS_W_DASL); log_debug ("%s:%s: message class is `%s'\n", SRCNAME, __func__, msgcls? msgcls:"[none]"); if (!msgcls) { return -1; } if ( !( !strncmp (msgcls, "IPM.Note.GpgOL", 14) && (!msgcls[14] || msgcls[14] == '.') ) ) { xfree (msgcls); log_error ("%s:%s: Message processed but not our class. Bug.", SRCNAME, __func__); return -1; } mail = Mail::getMailForItem (mailitem); if (!mail) { xfree (msgcls); log_error ("%s:%s: No mail object for mailitem. Bug.", SRCNAME, __func__); return -1; } is_smime = mail->isSMIME_m (); message = get_oom_base_message (mailitem); attachments = get_oom_object (mailitem, "Attachments"); if (!message) { log_error ("%s:%s: No message object.", SRCNAME, __func__); goto done; } if (!attachments) { log_error ("%s:%s: No attachments object.", SRCNAME, __func__); goto done; } msgtype = mapi_get_message_type (message); if (msgtype != MSGTYPE_GPGOL_PGP_MESSAGE && msgtype != MSGTYPE_GPGOL_MULTIPART_ENCRYPTED && msgtype != MSGTYPE_GPGOL_MULTIPART_SIGNED && msgtype != MSGTYPE_GPGOL_OPAQUE_ENCRYPTED && msgtype != MSGTYPE_GPGOL_OPAQUE_SIGNED) { log_error ("%s:%s: Revert not supported for msgtype: %i", SRCNAME, __func__, msgtype); goto done; } count = get_oom_int (attachments, "Count"); to_delete = (LPDISPATCH*) xmalloc (count * sizeof (LPDISPATCH)); /* Yes the items start at 1! */ for (i = 1; i <= count; i++) { LPDISPATCH attachment; attachtype_t att_type; if (gpgrt_asprintf (&item_str, "Item(%i)", i) == -1) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); goto done; } memdbg_alloc (item_str); attachment = get_oom_object (attachments, item_str); xfree (item_str); if (!attachment) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); goto done; } if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type)) { att_type = ATTACHTYPE_FROMMOSS; } switch (att_type) { case ATTACHTYPE_PGPBODY: { /* Restore Body */ char *body = get_pa_string (attachment, PR_ATTACH_DATA_BIN_DASL); if (!body) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); gpgol_release (attachment); goto done; } log_debug ("%s:%s: Restoring pgp-body.", SRCNAME, __func__); if (put_oom_string (mailitem, "Body", body)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); xfree (body); gpgol_release (attachment); goto done; } body_restored = 1; xfree (body); to_delete[del_cnt++] = attachment; break; } /* No break we also want to delete that. */ case ATTACHTYPE_MOSS: { char *mime_tag = get_pa_string (attachment, PR_ATTACH_MIME_TAG_DASL); if (!mime_tag) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); } else if (msgtype == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED && !strcmp (mime_tag, "application/octet-stream")) { /* This is the body attachment of a multipart encrypted message. Rebuild the message. */ to_restore = attachment; to_delete[del_cnt++] = attachment; } else if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED && mime_tag && !strcmp (mime_tag, "multipart/signed")) { /* This is the MIME formatted MOSS attachment of a multipart signed message. Rebuild the MIME structure from that. This means treating it as a MOSSTMPL */ mosstmpl_found = 1; } else if (is_smime) { /* Same here. No restoration but just rebuilding from the attachment. */ mosstmpl_found = 1; } else { log_oom ("%s:%s: Skipping attachment with tag: %s", SRCNAME, __func__, mime_tag); to_delete[del_cnt++] = attachment; } xfree (mime_tag); break; } case ATTACHTYPE_FROMMOSS: case ATTACHTYPE_FROMMOSS_DEC: { to_delete[del_cnt++] = attachment; break; } case ATTACHTYPE_MOSSTEMPL: /* This is a newly created attachment containing a MIME structure other clients could handle */ { if (mosstmpl_found) { log_error ("More then one mosstempl."); goto done; } mosstmpl_found = 1; break; } default: to_delete[del_cnt++] = attachment; } } if (to_restore && !mosstmpl_found) { log_debug ("%s:%s: Restoring from MOSS.", SRCNAME, __func__); if (restore_msg_from_moss (message, to_restore, msgtype, msgcls)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); } else { to_restore = NULL; } } if (to_restore || mosstmpl_found) { HRESULT hr; SPropValue prop; /* Message was either restored or the only attachment is the mosstmplate in which case we need to activate the MultipartSigned magic.*/ prop.ulPropTag = PR_MESSAGE_CLASS_A; if (is_smime) { #if 0 /* FIXME this does not appear to work somehow. */ if (opt.enable_smime) { prop.Value.lpszA = (char*) "IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned"; hr = HrSetOneProp (message, &prop); } else #endif { ULONG tag; if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED) prop.Value.lpszA = (char*) "IPM.Note.SMIME.MultipartSigned"; else prop.Value.lpszA = (char*) "IPM.Note.SMIME"; hr = HrSetOneProp (message, &prop); if (!get_gpgolmsgclass_tag (message, &tag)) { SPropTagArray proparray; proparray.cValues = 1; proparray.aulPropTag[0] = tag; hr = message->DeleteProps (&proparray, NULL); if (hr) { log_error ("%s:%s: deleteprops smime failed: hr=%#lx\n", SRCNAME, __func__, hr); } } } } else if (msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED) { prop.Value.lpszA = (char*) "IPM.Note.SMIME.MultipartSigned"; hr = HrSetOneProp (message, &prop); } else { prop.Value.lpszA = (char*) "IPM.Note.SMIME"; hr = HrSetOneProp (message, &prop); } if (hr) { log_error ("%s:%s: error setting the message class: hr=%#lx\n", SRCNAME, __func__, hr); goto done; } /* Backup the real message class */ if (!is_smime || opt.enable_smime) { if (mapi_set_gpgol_msg_class (message, msgcls)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); goto done; } } else if (is_smime && !opt.enable_smime) { /* SMIME is disabled remove our categories. */ mail->removeCategories_o (); } } result = 0; done: /* Do the deletion body wipe even on error. */ for (i = 0; i < del_cnt; i++) { LPDISPATCH attachment = to_delete[i]; if (attachment == to_restore) { /* If restoring failed to restore is still set. In that case do not delete the MOSS attachment to avoid data loss. */ continue; } /* Delete the attachments that are marked to delete */ if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); result = -1; } } if (!body_restored && put_oom_string (mailitem, "Body", "")) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); result = -1; } for (i = 0; i < del_cnt; i++) { gpgol_release (to_delete[i]); } xfree (to_delete); gpgol_release (attachments); xfree (msgcls); if (!result && finalize_mapi (message)) { log_error ("%s:%s: Finalize failed.", SRCNAME, __func__); result = -1; } gpgol_release (message); return result; } diff --git a/src/revert.h b/src/revert.h index 5ebc921..2de6974 100644 --- a/src/revert.h +++ b/src/revert.h @@ -1,32 +1,32 @@ /* revert.h - Declarations for revert.cpp. * Copyright (C) 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 . */ #ifndef REVERT_H #define REVERT_H -EXTERN_C LONG __stdcall gpgol_message_revert (LPMESSAGE message, +EXTERN_C LONG __stdcall gpgol_message_revert (LPMESSAGE message, LONG do_save, ULONG save_flags); EXTERN_C LONG __stdcall gpgol_mailitem_revert (LPDISPATCH mailitem); EXTERN_C LONG __stdcall gpgol_folder_revert (LPDISPATCH mapifolderobj); #endif /*REVERT_H*/ diff --git a/src/rfc822parse.h b/src/rfc822parse.h index db8ad37..98df608 100644 --- a/src/rfc822parse.h +++ b/src/rfc822parse.h @@ -1,89 +1,89 @@ /* rfc822parse.h - Simple mail and MIME parser * Copyright (C) 1999 Werner Koch, Duesseldorf * Copyright (C) 2003, g10 Code GmbH * * This program 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. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef RFC822PARSE_H #define RFC822PARSE_H #ifdef __cplusplus extern "C" { #if 0 } #endif #endif struct rfc822parse_context; typedef struct rfc822parse_context *rfc822parse_t; -typedef enum +typedef enum { RFC822PARSE_OPEN = 1, RFC822PARSE_CLOSE, RFC822PARSE_CANCEL, RFC822PARSE_T2BODY, RFC822PARSE_FINISH, RFC822PARSE_RCVD_SEEN, RFC822PARSE_LEVEL_DOWN, RFC822PARSE_LEVEL_UP, RFC822PARSE_BOUNDARY, RFC822PARSE_LAST_BOUNDARY, RFC822PARSE_BEGIN_HEADER, RFC822PARSE_PREAMBLE, RFC822PARSE_EPILOGUE - } + } rfc822parse_event_t; struct rfc822parse_field_context; typedef struct rfc822parse_field_context *rfc822parse_field_t; typedef int (*rfc822parse_cb_t) (void *opaque, rfc822parse_event_t event, rfc822parse_t msg); rfc822parse_t rfc822parse_open (rfc822parse_cb_t cb, void *opaque_value); void rfc822parse_close (rfc822parse_t msg); void rfc822parse_cancel (rfc822parse_t msg); int rfc822parse_finish (rfc822parse_t msg); int rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length); char *rfc822parse_get_field (rfc822parse_t msg, const char *name, int which, size_t *valueoff); const char *rfc822parse_enum_header_lines (rfc822parse_t msg, void **context); rfc822parse_field_t rfc822parse_parse_field (rfc822parse_t msg, const char *name, int which); void rfc822parse_release_field (rfc822parse_field_t field); const char *rfc822parse_query_parameter (rfc822parse_field_t ctx, const char *attr, int lower_value); const char *rfc822parse_query_media_type (rfc822parse_field_t ctx, const char **subtype); #ifdef __cplusplus } #endif #endif /*RFC822PARSE_H */ diff --git a/src/w32-gettext.h b/src/w32-gettext.h index 4dfe40c..9b280da 100644 --- a/src/w32-gettext.h +++ b/src/w32-gettext.h @@ -1,100 +1,100 @@ /* w32-gettext.h - A simple gettext implementation for Windows targets. Copyright (C) 2005 g10 Code GmbH This file is part of libgpg-error. - + libgpg-error 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. - + libgpg-error 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 libgpg-error; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if ENABLE_NLS #include #if !defined LC_MESSAGES && !(defined __LOCALE_H || (defined _LOCALE_H && defined __sun)) # define LC_MESSAGES 1729 #endif #ifdef __cplusplus extern "C" { #if 0 } #endif #endif /* Specify that the DOMAINNAME message catalog will be found in DIRNAME rather than in the system locale data base. */ char *bindtextdomain (const char *domainname, const char *dirname); const char *gettext (const char *msgid); const char *utf8_gettext (const char *msgid); char *textdomain (const char *domainname); char *dgettext (const char *domainname, const char *msgid); /* Return the localname as used by gettext. The return value will never be NULL. */ const char *gettext_localename (void); /* A pseudo function call that serves as a marker for the automated extraction of messages, but does not call gettext(). The run-time translation is done at a different place in the code. The argument, String, should be a literal string. Concatenated strings and other string expressions won't work. The macro's expansion is not parenthesized, so that it is suitable as initializer for static 'char[]' or 'const char[]' variables. */ #define gettext_noop(String) String #else /* ENABLE_NLS */ static inline const char *gettext_localename (void) { return ""; } #endif /* !ENABLE_NLS */ /* Conversion function. */ char *_wchar_to_utf8 (const wchar_t *string); wchar_t *_utf8_to_wchar (const char *string); char *utf8_to_native (const char *string); char *native_to_utf8 (const char *string); #define utf8_to_wchar(VAR1) ({wchar_t *retval; \ retval = _utf8_to_wchar (VAR1); \ if ((opt.enable_debug & DBG_TRACE) && \ (opt.enable_debug & DBG_DATA) && \ (opt.enable_debug & DBG_MEMORY)) \ { \ log_debug ("%s:%s:%i wchar_t alloc %p:%S", \ SRCNAME, __func__, __LINE__, retval, retval); \ } \ retval;}) #define wchar_to_utf8(VAR1) ({char *retval; \ retval = _wchar_to_utf8 (VAR1); \ if ((opt.enable_debug & DBG_TRACE) && \ (opt.enable_debug & DBG_DATA) && \ (opt.enable_debug & DBG_MEMORY)) \ { \ log_debug ("%s:%s:%i char utf8 alloc %p:%s", \ SRCNAME, __func__, __LINE__, retval, retval); \ } \ retval;}) #ifdef __cplusplus } #include std::string wchar_to_utf8_string (const wchar_t *string); #endif diff --git a/src/xmalloc.h b/src/xmalloc.h index f2cf877..a960266 100644 --- a/src/xmalloc.h +++ b/src/xmalloc.h @@ -1,93 +1,93 @@ /* xmalloc.h - xmalloc prototypes * Copyright (C) 2006 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 . */ #ifndef XMALLOC_H #define XMALLOC_H #ifdef __cplusplus extern "C" { #if 0 } #endif #endif /*-- common.c --*/ #define xmalloc(VAR1) ({void *retval; \ retval = _xmalloc(VAR1); \ if ((opt.enable_debug & DBG_MEMORY)) \ { \ memdbg_alloc (retval); \ if ((opt.enable_debug & DBG_TRACE)) \ memset (retval, 'X', VAR1); \ } \ retval;}) #define xcalloc(VAR1, VAR2) ({void *retval; \ retval = _xcalloc(VAR1, VAR2); \ if ((opt.enable_debug & DBG_MEMORY)) \ { \ memdbg_alloc (retval);\ } \ retval;}) #define xrealloc(VAR1, VAR2) ({void *retval; \ retval = _xrealloc (VAR1, VAR2); \ if ((opt.enable_debug & DBG_MEMORY)) \ { \ memdbg_alloc (retval);\ memdbg_free ((void*)VAR1); \ } \ retval;}) #define xfree(VAR1) \ { \ if (VAR1 && (opt.enable_debug & DBG_MEMORY) && !memdbg_free (VAR1)) \ log_debug ("%s:%s:%i %p freed here", \ log_srcname (__FILE__), __func__, __LINE__, VAR1); \ _xfree (VAR1); \ } #define xstrdup(VAR1) ({char *retval; \ retval = _xstrdup (VAR1); \ if ((opt.enable_debug & DBG_MEMORY)) \ { \ memdbg_alloc ((void *)retval);\ } \ retval;}) #define xwcsdup(VAR1) ({wchar_t *retval; \ retval = _xwcsdup (VAR1); \ if ((opt.enable_debug & DBG_MEMORY)) \ { \ memdbg_alloc ((void *)retval);\ } \ retval;}) void* _xmalloc (size_t n); void* _xcalloc (size_t m, size_t n); void *_xrealloc (void *a, size_t n); char* _xstrdup (const char *s); wchar_t * _xwcsdup (const wchar_t *s); void _xfree (void *p); void out_of_core (void); #ifdef __cplusplus } #endif #endif /*XMALLOC_H*/