Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34768404
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
162 KB
Subscribers
None
View Options
diff --git a/doc/gpgol.texi b/doc/gpgol.texi
index b68e1e4..89f7657 100644
--- a/doc/gpgol.texi
+++ b/doc/gpgol.texi
@@ -1,577 +1,581 @@
\input texinfo
@documentencoding ISO-8859-1
@setfilename gpgol.info
@include version.texi
@settitle The GpgOL Technical Manual
@dircategory GnuPG Plugin
@direntry
* gpgol: (gpgol). An Outlook Plugin for GnuPG.
@end direntry
@macro clnt
@sc{c:} @c
@end macro
@macro srvr
@sc{s:} @c
@end macro
@c Unify some of the indices.
@syncodeindex tp fn
@syncodeindex pg fn
@copying
This is @cite{The GpgOL Technical Manual} for @acronym{GpgOL} (version
@value{VERSION}, @value{UPDATED-MONTH}).
@iftex
Published by g10 Code GmbH@*
Bergstr. 3a@*
40699 Erkrath, Germany
@end iftex
Copyright @copyright{} 2007, 2008 g10 Code GmbH
@quotation
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. The text of the license can be found in the
section entitled ``Copying''.
@end quotation
@end copying
@c
@c Titlepage
@c
@setchapternewpage odd
@titlepage
@title The GpgOL Technical Manual
@subtitle Version @value{VERSION}
@subtitle @value{UPDATED-MONTH}
@sp 3
@sp 3
@author Werner Koch (@email{wk@@gnupg.org})
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@ifnothtml
@contents
@page
@end ifnothtml
@c @ifhtml
@c @center @image{logo}
@c @end ifhtml
@ifnottex
@node Top
@top
@insertcopying
@noindent
This file documents @acronym{GpgOL}; a GnuPG plugin for Microsoft's
Outlook MUA.
@end ifnottex
@menu
* Introduction:: How to use this manual.
* External Helper:: Description of external helper tools.
* MAPI Properties:: MAPI Properties used by GpgOL.
* Registry Settings:: How GpgOL uses the Registry.
* MAPI Providers:: What MAPI Storage or Transport providers
can do to help GpgOL.
Appendices
* Copying:: The GNU General Public License says how you
can copy and share this manual.
Indices
* Concept Index:: Index of concepts and programs.
* Function and Data Index:: Index of functions, variables and data types.
@end menu
@ifhtml
@page
@summarycontents
@contents
@end ifhtml
@c
@c I N T R O
@c
@node Introduction
@chapter Introduction
THE DESCRIPTION HERE IS OUTDATED AND NEEDS TO BE REVISED.
To debug GpgOL you should set the Registry entry
@code{HKCU\Software\Gnu\GpgOL:enableDebug} to the string value @code{1}:
@cartouche
@example
[HKEY_CURRENT_USER\Software\GNU\GpgOL]
"enableDebug"="1"
@end example
@end cartouche
This allows easy setting of a debug file by using the extended options
menu and enables a few extra menu items.
@c
@c P R O T O C O L D E S C R I P T I O N
@c
-@node Assuan Protocol
-@chapter Description of the UI Server Protocol
+@node External Helper
+@chapter Description of external helper tools.
Cryptographic operations are either done using the GPGME library or by
-utilizing external helper tools which part of the `Kleopatra' frontend.
+utilizing external helper tools which are part of the `Kleopatra'
+frontend.
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@c
@c M A P I P r o p e r t i e s
@c
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@node MAPI Properties
@chapter MAPI Properties used by GpgOL
GpgOL uses a some custom MAPI properties in the named properties range.
Thus their actual numbers are determined at runtime and only the names
should be used. The GUID assigned to these properties is
@code{31805ab8-3e92-11dc-879c-00061b031004}.
@table @code
@item GpgOL Msg Class
This is a STRING8 property used as a override for PR_MESSAGE_CLASS.
GpgOL uses this internally for creating messages.
@item GpgOL Old Msg Class
This is a STRING8 property which saves the original PR_MESSAGE_CLASS
before GpgOL changes it.
@item GpgOL Attach Type
This is a property of type LONG and used to further describe the
attachments created by GpgOL. These values are used:
@table @asis
@item ATTACHTYPE_MOSS = 1
The attachment contains the original MOSS message. That is either an
S/MIME or a PGP/MIME message in its original RFC-2822 format (but only
with the relevant MIME parts of the main header).
@item ATTACHTYPE_FROMMOSS = 2
The attachment has been created from the original MOSS attachment. It
will automagically be recreated as needed. If the attachment has
been created from an encrypted message, it is saved re-encrypted under
a non-permanent session key. This session key is valid as long as the
current Outlook porcess exists.
@item ATTACHTYPE_MOSSTEMPL = 3
The attachment has been created in the course of sending a message.
@item ATTACHTYPE_PGPBODY = 4
The attachment contains the original PGP message body of PGP inline
encrypted messages. We need to save this away because it may happen
that in the course of displaying the plaintext Outlook overwrites the
actual body due to internal syncronization.
@end table
@item GpgOL Sig Status
This is a property of type STRING8 and used to cache the result of the
last signature verification. It is used with the actual message and
consists of a single character, a space and a human readable string
(utf-8 encoded). The first character is used as a machine processable
flag indicating the status. These values are defined:
@table @code
@item #
The message is not of interest to us. GpgOL may flag any message with
this signature status to avoid extra processing for messages already
known not to need any processing by GpgOL.
@item @@
The message has been created and signed or encrypted by GpgOL.
@item ?
The signature status has not been checked. This is for example used
if the public key to be used for the verification could not be found.
@item !
The signature verified okay and is deemed to be fully valid.
@item ~
The signature was not fully verified. This often means that the full
result information of the signature verification needs to be
considered to decide the actual validity. Used for example if the
signing key has expired
@item -
The signature is bad. Either this means the message has been tampered
with or an intermediate message relay has accidently changed
the message (e.g. due to recoding).
@end table
@item GpgOL Protect IV
This binary property is used to store the initialization vector of an
re-encrypted attachment. The existence of this property indicates that
the attachment has been encrypted under the non-permanent session key.
@item GpgOL Charset
This is a property of type STRING8 and used to describe the character
set of an attachment or of the body. If this propery is missing the
default of UTF-8 is assumed.
@item GpgOL Last Decrypted
This binary property is used on the message to save a session marker to
tell GpgOL whether the message as already been decrypted. If this
property does not exists or the session marker does not macth the one of
the current session, GpgOL needs to decrypt it again.
@item GpgOL MIME Info
This property is of type STRING8 and used to store the MIME structure of
the orginal message. The content are lines of colon delimited fields.
The specification has not yet been finished.
@item GpgOL Draft Info
This is a property of type STRING8 used to preserve crypto settings in a
draft message. For details see the function
@code{mapi_set_gpgol_draft_info}.
@end table
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@c
@c R e g i s t r y S e t t i n g s
@c
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@node Registry Settings
@chapter How GpgOL uses the Registry
This is a list of registry entries GpgOL knows about.
@table @code
@item HKLM\Software\GNU\GnuPG:Install Directory
This is used by GnuPG to describe the directory where GnupG has been
installed. GpgOL requires this to get the location of the localedir
which is used to show translated strings (@file{gpgol.mo}). It is
further used to check whether GnuPG has been installed at all.
@item HKCU\Software\GNU\GnuPG:UI Server
If the UI server could not be connected, GpgOL tries to start the one
given in this entry. It is assumed that the UI server is stored in the
@code{Install Directory} (as described above). This Registry entry
gives the actual command name relative to this directory. If the key
does not exist, is is first searched below @code{HKLM} and then it
defaults to @code{kleopatra.exe}.
@item HKCU\Software\GNU\GpgOL:enableDebug
Setting this key to the string @code{1} enables a few extra features in
the UI, useful only for debugging. Setting it to values larger than 1
make the log file output more verbose; these are actually bit flags
according to the following table (which may change with any release):
@table @code
@item 2 (0x0002) (ioworker)
Tell what the Assuan I/O scheduler is doing.
@item 4 (0x0004) (ioworker-extra)
Even more verbose Assuan I/O scheduler reporting.
@item 8 (0x0008) (filter)
Tell what the filter I/O system is doing.
@item 16 (0x0010) (filter-extra)
Tell how the filter I/O locks the resources.
@item 32 (0x0020) (memory)
Tell about resource allocation.
@item 64 (0x0040) (commands)
Tell about command events.
@item 128 (0x0080) (mime-parser)
Tell what the MIME parser is doing
@item 256 (0x0100) (mime-data)
Print data lines while parsing MIME.
@item 512 (0x0200) (oom)
Outlook Object Model reporting.
@item 1024 (0x0400) (oom-extra)
Verbose OOM allocation and advising reporting.
@end table
You may use the regular C-syntax for entering the value. As an
alternative you may use the names of the flags, separated by space or
comma.
@item HKCU\Software\GNU\GpgOL:logFile
If the value is not empty, GpgOL takes this as a log file and appends
debug information to this file. The file may get very large.
@item HKCU\Software\GNU\GpgOL:compatFlags
This is a string consisting of @code{0} and @code{1} to enable certain
compatibility flags. Not generally useful; use the source for a
description.
@item HKCU\Software\GNU\GpgOL:enableSmime
@itemx HKCU\Software\GNU\GpgOL:defaultProtocol
@itemx HKCU\Software\GNU\GpgOL:encryptDefault
@itemx HKCU\Software\GNU\GpgOL:signDefault
@itemx HKCU\Software\GNU\GpgOL:previewDecrypt
@itemx HKCU\Software\GNU\GpgOL:storePasswdTime
@itemx HKCU\Software\GNU\GpgOL:encodingFormat
@itemx HKCU\Software\GNU\GpgOL:defaultKey
@itemx HKCU\Software\GNU\GpgOL:enableDefaultKey
@itemx HKCU\Software\GNU\GpgOL:preferHtml
These registry keys store the values from the configuration dialog.
@item HKCU\Software\GNU\GpgOL:svnRevision
Obsolete since 1.1.3.
@item HKCU\Software\GNU\GpgOL:gitCommit
When leaving GpgOL's options dialog, the GIT commit id of the current
version will be stored in this entry. This is used to display a note
after software upgrades.
+@item HKCU\Software\GNU\GpgOL:disableTitusHandling
+If set to the string value of "1" the detection and special processing
+of Titus processed mails is disabled.
@end table
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@c
@c MAPI Providers
@c
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@node MAPI Providers
@chapter What MAPI Storage or Transport providers can do to help GpgOL
GpgOL uses some tricks to make decryption of OpenPGP message better fit
into the Outlook framework. This is due to a lack of proper Plugin API
for Outlook and because some features of Outlook --- meant as a security
measure --- hinder a better implementation. That is not to say that
Outlook will be less secure when used with GpgOL --- to the opposite:
Due to encryption and digital signature reading and sending mail with
GpgOL support can be much more secure than using Outlook as is.
There are some points where custom MAPI storage or transport
providers can help GpgOL to gain better performance and to make it more
secure.
@section MAPI Message Class Renaming
To implement S/MIME processing using its own engine, GpgOL needs to
inhibit Outlook from doing the S/MIME before the message is passed to
the ECE hooks. As usual this is done by changing the message class
(PR_MESSAGE_CLASS). For new message this happens right away at the
OnDelivery hook; for already existing messages GpgOL tries to detect
the case at several other places (which is less reliable but in general
works).
@noindent
The renaming is very straightforward:
@itemize @bullet
@item
If the message class is just @code{IPM.Note} extra tests are done to
figure out a suitable message class. This yields one of these new
message classes:
@table @code
@item IPM.Note.GpgOL.OpaqueSigned
@item IPM.Note.GpgOL.OpaqueEncrypted
@item IPM.Note.GpgOL.ClearSigned
@item IPM.Note.GpgOL.PGPMessage
@end table
@item
If the message class is either @code{IPM.Note.SMIME} or that one
followed by a dot and a subclass, the @code{SMIME} string is replaced
by @code{GpgOL}.
@item
If the message class is @code{IPM.Note.Secure.CexSig} or
@code{IPM.Note.Secure.CexEnc} the class is changed depending on other
information to one of:
@table @code
@item IPM.Note.GpgOL.OpaqueSigned
@item IPM.Note.GpgOL.OpaqueEncrypted
@item IPM.Note.GpgOL.MultipartSigned
@item IPM.Note.GpgOL
@item IPM.Note.GpgOL.ClearSigned
@item IPM.Note.GpgOL.PGPMessage
@end table
@end itemize
To revert these message class changes one need to replace any message
class prefix of @code{IPM.Note.GpgOL} by @code{IPM.Note.SMIME}. There
are two caveats however:
@itemize
@item
GpgOL copies or flags the original MOSS attachment as created by
Outlook to a new attachment with the attach type set to ATTACHTYPE_MOSS.
If such an attachment exists it should be converted back to the original
attachment (or used to convert the message back to RFC-822). It might
however not exist and in this case there should be only one attachment
at all as created by Outlook, so no further changes are required.
@item
Inline PGP encrypted mails (@code{IPM.Note.GpgOL.PGPMessage}) might have
a wrong PR_BODY. This condition can be detected by the existance of an
attachment named @file{gpgolPGP.dat}, flagged as hidden and with the
attach type ATTACHTYPE_PGPBODY (See above under MAPI Properties). If
such an attachment exists, it should be copied to PR_BODY and may then
be deleted.
@end itemize
@noindent
Note that reverting original CryptoEx message classes (@code{CexSig}
etc.) back is not possible. They are identical to GpgOL messages.
@section MAPI Attachment Processing
GpgOL creates a couple of attachments for the purpose of storing a
parsed mail and to allow Outlook to display attachments in the usual way
without sending them as plaintext to the storage. The attachments are
only stored on the local disk while being opened from the attachment's
context menu for viewing. Almost all these attachments are ephemeral and
may be deleted when not displayed. GpgOL re-creates them by parsing the
original message if neeeded. In fact they are always re-created after
Outlook as been started again. This is because the attachments holding
the plaintext are symmetrical encrypted with an ephemeral session key,
only valid as long as Outlook runs.
FIXME: Needs more documentation.
@section MAPI PR_BODY Processing
GpgOL does not use the PR_BODY property. This is because internal
Outlook syncronisation may change that property after the plaintext of a
message has been displayed. In general this is not a problem because
the messages processed by GpgOL do not use that property (the orginal
S/MIME and PGP/MIME message is stored in attachments). However, there
is one exception: Inline PGP message (in contrast to the modern PGP/MIME
messages) are conveyed in the PR_BODY. To avoid changing that orginal
mail, GpgOL copies such a body to a new attachment named
@file{gpgolPGP.dat}, flags it as hidden and sets the attach type to
ATTACHTYPE_PGPBODY (See above under MAPI Properties). That attachment
may never be deleted!
Due to internal OL syncronisation, plaintext data may end up in PR_BODY,
GpgOL tries hard to delete PR_BODY so that it nevers shows up in the
MAPI storage. However this is hard and here a storage provider can help
by deleting PR_BODY under one of these conditions:
@itemize @bullet
@item
If the message class is either @code{IPM.Note.GpgOL.MultipartEncrypted}
or @code{IPM.Note.GpgOL.OpaqueEncrypted} and in addition the message has
a property @code{GpgOL Last Decrypted} (with any value), delete the
properties @code{PR_BODY} and @code{PR_BODY_HTML}.
@item
If the message class is @code{IPM.Note.GpgOL.PGPMessage} and an
attachment of ATTACHTYPE_PGPBODY with a filename @file{gpgolPGP.dat}
exists, delete the properties @code{PR_BODY} and @code{PR_BODY_HTML}.
@end itemize
Instead of deleting it should be sufficient to make sure
that such PR_BODYs are not updated and don't make it to the disk or a
strage server.
Implementing such a feature would really help with end-to-end encryption
where the security policy requires that the plaintext of an encrypted
message will never be stored on a disk or leave the local machine.
@section Filtering GpgOL internal properties
To avoid attacks by importing TNEF data with certain GpgOL internal
properties, a MAPI provider may want to filter them out when receiving a
message from an external location. It is not yet clear whether this is
really needed.
FIXME.
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@c
@c A P P E N D I X
@c
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@include gpl.texi
@c
@c I N D E X E S
@c
@node Concept Index
@unnumbered Concept Index
@printindex cp
@node Function and Data Index
@unnumbered Function and Data Index
@printindex fn
@bye
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@c
@c E D I T O R ' S A T T I C
@c
@c xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
What about the message class mangling?
* On receiving a new message GpgOL checks the MAPI message class
property of the message and if this is an S/MIME message class
("IPM.Note.SMIME"), it is changed to a GpgOL specific one
("IPM.Note.GpgOL"). This change is required so that OL does not not
apply its own S/MIME handler to the message but leaves it unchanged in
the message store.
* For ease of implementation the same thing applies to PGP messgaes,
although OL would not touch these messages.
* When reading a message GpgOL quickly checks the message class and if
it is "IPM.Note.GpgOL" it will hook itself into the code path and
decrypt/verify the message.
* Messages already in the message store before GpgOL was installed are
handled differently: Here an Outlook specific event is used to change
the message class when browsing the messages folder. This code path
is not fully ready as it requires the installation of an ECF(ile)
which has to be done manually as of now.
* If GpgOL is deinstalled, the existing S/MIME messages can't be
decrypted or verified by Outlook's internal S/MIME support.
Multipart/signed messages are still readable, though. We plan to add
a little tool for changing the GpgOL message classes back to
"IPM.Note.SMIME" which in turn allows using internal S/MIME support
again.
@c Local Variables:
@c coding: latin-1
@c End:
diff --git a/src/common_indep.h b/src/common_indep.h
index ff62fbf..0db9515 100644
--- a/src/common_indep.h
+++ b/src/common_indep.h
@@ -1,356 +1,373 @@
#ifndef COMMON_INDEP_H
#define COMMON_INDEP_H
/* common_indep.h - Common, platform indepentent routines used by GpgOL
* Copyright (C) 2005, 2007, 2008 g10 Code GmbH
* Copyright (C) 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
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <gpgme.h>
#include "xmalloc.h"
#include "debug.h"
#include "memdbg.h"
#ifdef HAVE_W32_SYSTEM
/* Not so independenent ;-) need this for logging HANDLE */
# include <windows.h>
#endif
/* The Registry key used by Gpg4win. */
#define GPG4WIN_REGKEY_2 "Software\\GNU\\GnuPG"
#define GPG4WIN_REGKEY_3 "Software\\Gpg4win"
/* Identifiers for the protocol. We use different one than those use
by gpgme. FIXME: We might want to define an unknown protocol to
non-null and define such a value also in gpgme. */
typedef enum
{
PROTOCOL_UNKNOWN = 0,
PROTOCOL_OPENPGP = 1000,
PROTOCOL_SMIME = 1001
}
protocol_t;
/* Possible options for the recipient dialog. */
enum
{
OPT_FLAG_TEXT = 2,
OPT_FLAG_FORCE = 4,
OPT_FLAG_CANCEL = 8
};
typedef enum
{
GPG_FMT_NONE = 0, /* do not encrypt attachments */
GPG_FMT_CLASSIC = 1, /* encrypt attachments without any encoding */
GPG_FMT_PGP_PEF = 2 /* use the PGP partioned encoding format (PEF) */
}
gpgol_format_t;
/* Type of a message. */
typedef enum
{
OPENPGP_NONE = 0,
OPENPGP_MSG,
OPENPGP_SIG,
OPENPGP_CLEARSIG,
OPENPGP_PUBKEY, /* Note, that this type is only partly supported */
OPENPGP_SECKEY /* Note, that this type is only partly supported */
}
openpgp_t;
/* The list of message types we support in GpgOL. */
typedef enum
{
MSGTYPE_UNKNOWN = 0,
MSGTYPE_SMIME, /* Original SMIME class. */
MSGTYPE_GPGOL,
MSGTYPE_GPGOL_MULTIPART_SIGNED,
MSGTYPE_GPGOL_MULTIPART_ENCRYPTED,
MSGTYPE_GPGOL_OPAQUE_SIGNED,
MSGTYPE_GPGOL_OPAQUE_ENCRYPTED,
MSGTYPE_GPGOL_CLEAR_SIGNED,
MSGTYPE_GPGOL_PGP_MESSAGE,
MSGTYPE_GPGOL_WKS_CONFIRMATION
}
msgtype_t;
typedef enum
{
ATTACHTYPE_UNKNOWN = 0,
ATTACHTYPE_MOSS = 1, /* The original MOSS message (ie. a
S/MIME or PGP/MIME message. */
ATTACHTYPE_FROMMOSS = 2, /* Attachment created from MOSS. */
ATTACHTYPE_MOSSTEMPL = 3, /* Attachment has been created in the
course of sending a message */
ATTACHTYPE_PGPBODY = 4, /* Attachment contains the original
PGP message body of PGP inline
encrypted messages. */
ATTACHTYPE_FROMMOSS_DEC = 5 /* A FROMMOSS attachment that has been
temporarily decrypted and needs to be
encrypted before it is written back
into storage. */
}
attachtype_t;
/* An object to collect information about one MAPI attachment. */
struct mapi_attach_item_s
{
int end_of_table; /* True if this is the last plus one entry of
the table. */
void *private_mapitable; /* Only for use by mapi_release_attach_table. */
int mapipos; /* The position which needs to be passed to
MAPI to open the attachment. -1 means that
there is no valid attachment. */
int method; /* MAPI attachment method. */
char *filename; /* Malloced filename of this attachment or NULL. */
/* Malloced string with the MIME attrib or NULL. Parameters are
stripped off thus a compare against "type/subtype" is
sufficient. */
char *content_type;
/* If not NULL the parameters of the content_type. */
const char *content_type_parms;
/* If not NULL the content_id */
char *content_id;
/* The attachment type from Property GpgOL Attach Type. */
attachtype_t attach_type;
};
typedef struct mapi_attach_item_s mapi_attach_item_t;
/* Passphrase callback structure. */
struct passphrase_cb_s
{
gpgme_key_t signer;
gpgme_ctx_t ctx;
char keyid[16+1];
char *user_id;
char *pass;
int opts;
int ttl; /* TTL of the passphrase. */
unsigned int decrypt_cmd:1; /* 1 = show decrypt dialog, otherwise secret key
selection. */
unsigned int hide_pwd:1;
unsigned int last_was_bad:1;
};
/* Global options - initialized to default by main.c. */
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
struct opt_s
{
int enable_debug; /* Enable extra debug options. Values
larger than 1 increases the debug log
verbosity. */
int enable_smime; /* Enable S/MIME support. */
int encrypt_default; /* Encrypt by default. */
int sign_default; /* Sign by default. */
int prefer_html; /* Prefer html in html/text alternatives. */
int inline_pgp; /* Only for Addin. Use Inline PGP by default. */
int autoresolve; /* Autresolve keys with --locate-keys. */
int autosecure; /* Autmatically encrypt if locate returns enough validity. */
int reply_crypt; /* Only for Addin. Encrypt / Sign based on cryptostatus. */
int automation; /* General automation */
int autotrust; /* TOFU configured for GpgOL */
int sync_enc; /* Disabed async encryption */
int sync_dec; /* Disabed async decryption */
int prefer_smime; /* S/MIME prefered when autoresolving */
- int smime_html_warn_shown; /* Flag to save if unsigned smime warning was shown */
- int smime_insecure_reply_fw_allowed ; /* Flag to save if unsigned smime reply forwards
- should be allowed */
- int autoretrieve; /* Use --auto-key-retrieve. */
- int search_smime_servers; /* Search for S/MIME keys on all configured S/MIME keyservers
- for each new unknown mail */
+ int smime_html_warn_shown; /* Flag to save if unsigned smime warning
+ was shown */
+ int smime_insecure_reply_fw_allowed ; /* Flag to save if unsigned
+ smime reply forwards should
+ be allowed */
+ int autoretrieve; /* Use --auto-key-retrieve. */
+ int search_smime_servers; /* Search for S/MIME keys on all
+ configured S/MIME keyservers for each
+ new unknown mail */
int auto_unstrusted; /* Automatically encrypt even to untrusted keys. */
- int autoimport; /* Automatically import keys from headers or attachments. */
+ int autoimport; /* Automatically import keys from headers
+ or attachments. */
char *draft_key; /* Encrypt drafts with this key (fingerprint) */
int alwaysShowApproval; /* Always show the certificate approval dialog. */
- int combinedOpsEnabled; /* Enable S/MIME and OpenPGP combined operations. */
+ int combinedOpsEnabled; /* Enable S/MIME and OpenPGP combined
+ operations. */
int splitBCCMails; /* Split BCC recipients in their own mails. */
int encryptSubject; /* Encrypt the subject with protected headers. */
- int noSaveBeforeDecrypt; /* Do not save to MAPI before modifying the mail for decryption.
- While this can avoid plaintext leaks in case other plugins
- or server addins added properties that they really want
- to save and then bypass our save blocks but might
- cause excessive syncs. So better have this as a debug
- option. */
- int closeOnUnknownWriteEvent; /* Observations have shown write events which were
- announced by the event: 0xFC99 There might be
- an extremely rare plaintext leak associated with
- that. So we had code to close and discard changes
- to encrypted mails when we see that event. This option
- allows to activate this code if an Outlook or Exchange
- update or some special configuration make this neccessary. */
- char *smimeNoCertSigErr; /* Error to show when GpgOL is configured for S/MIME
- signing but no cert is found. */
-
- /* The forms revision number of the binary. */
- int forms_revision;
+ int noSaveBeforeDecrypt; /* Do not save to MAPI before modifying
+ the mail for decryption. While this
+ can avoid plaintext leaks in case
+ other plugins or server addins added
+ properties that they really want to
+ save and then bypass our save blocks
+ but might cause excessive syncs. So
+ better have this as a debug option. */
+
+ int closeOnUnknownWriteEvent; /* Observations have shown write
+ events which were announced by the
+ event: 0xFC99 There might be an
+ extremely rare plaintext leak
+ associated with that. So we had code
+ to close and discard changes to
+ encrypted mails when we see that
+ event. This option allows to activate
+ this code if an Outlook or Exchange
+ update or some special configuration
+ make this neccessary. */
+
+ char *smimeNoCertSigErr; /* Error to show when GpgOL is configured
+ for S/MIME signing but no cert is
+ found. */
+
+ int disable_titus_handling; /* Disable the code to detect and handle
+ Titus processed mails. */
+
+ int forms_revision; /* The forms revision number of the
+ binary. */
};
extern struct opt_s opt;
/* The state object used by b64_decode. */
struct b64_state_s
{
int idx;
unsigned char val;
int stop_seen;
int invalid_encoding;
};
typedef struct b64_state_s b64_state_t;
size_t qp_decode (char *buffer, size_t length, int *r_slbrk);
char *qp_encode (const char *input, size_t length, size_t* outlen);
void b64_init (b64_state_t *state);
size_t b64_decode (b64_state_t *state, char *buffer, size_t length);
char * b64_encode (const char *input, size_t length);
char *latin1_to_utf8 (const char *string);
char *mem2str (char *dest, const void *src, size_t n);
char *trim_spaces (char *string);
char *trim_trailing_spaces (char *string);
/* To avoid that a compiler optimizes certain memset calls away, these
macros may be used instead. */
#define wipememory2(_ptr,_set,_len) do { \
volatile char *_vptr=(volatile char *)(_ptr); \
size_t _vlen=(_len); \
while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \
} while(0)
#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len)
#define wipestring(_ptr) do { \
volatile char *_vptr=(volatile char *)(_ptr); \
while(*_vptr) { *_vptr=0; _vptr++; } \
} while(0)
void set_default_key (const char *name);
/*-- Convenience macros. -- */
#define DIM(v) (sizeof(v)/sizeof((v)[0]))
#define DIMof(type,member) DIM(((type *)0)->member)
/*-- Macros to replace ctype ones to avoid locale problems. --*/
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
/* Note this isn't identical to a C locale isspace() without \f and
\v, but works for the purposes used here. */
#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
/* The atoi macros assume that the buffer has only valid digits. */
#define atoi_1(p) (*(p) - '0' )
#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1))
#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2))
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
#define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2))
#define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A'))
#define tohex_lower(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'a'))
/***** Inline functions. ****/
/* Return true if LINE consists only of white space (up to and
including the LF). */
static inline int
trailing_ws_p (const char *line)
{
for ( ; *line && *line != '\n'; line++)
if (*line != ' ' && *line != '\t' && *line != '\r')
return 0;
return 1;
}
/* An strcmp variant with the compare ending at the end of B. */
static inline int
tagcmp (const char *a, const char *b)
{
return strncmp (a, b, strlen (b));
}
#ifdef HAVE_W32_SYSTEM
extern HANDLE log_mutex;
#endif
/***** Missing functions. ****/
#ifndef HAVE_STPCPY
static inline char *
_gpgol_stpcpy (char *a, const char *b)
{
while (*b)
*a++ = *b++;
*a = 0;
return a;
}
#define stpcpy(a,b) _gpgol_stpcpy ((a), (b))
#endif /*!HAVE_STPCPY*/
/* The length of the boundary - the buffer needs to be allocated one
byte larger. */
#define BOUNDARYSIZE 20
char *generate_boundary (char *buffer);
#ifdef __cplusplus
}
#include <string>
#include <gpgme++/data.h>
struct autocrypt_s
{
bool exists = false;
std::string pref;
std::string addr;
GpgME::Data data;
};
/* A general way to transfer some header infos around
without having to parse them multiple times. */
struct header_info_s
{
std::string boundary;
autocrypt_s acInfo;
};
#endif
#endif // COMMON_INDEP_H
diff --git a/src/main.c b/src/main.c
index ad764bb..df37028 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,385 +1,386 @@
/* main.c - DLL entry point
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <windows.h>
#include <wincrypt.h>
#include <ctype.h>
#include <winnls.h>
#include <unistd.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "mymapi.h"
/* Local function prototypes. */
static char *get_locale_dir (void);
static void drop_locale_dir (char *locale_dir);
/* The major version of Outlook we are attached to */
int g_ol_version_major;
void
i18n_init (void)
{
char *locale_dir;
#ifdef ENABLE_NLS
# ifdef HAVE_LC_MESSAGES
setlocale (LC_TIME, "");
setlocale (LC_MESSAGES, "");
# else
setlocale (LC_ALL, "" );
# endif
#endif
locale_dir = get_locale_dir ();
if (locale_dir)
{
bindtextdomain (PACKAGE_GT, locale_dir);
drop_locale_dir (locale_dir);
}
textdomain (PACKAGE_GT);
}
/* Entry point called by DLL loader. */
int WINAPI
DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved)
{
(void)reserved;
if (reason == DLL_PROCESS_ATTACH)
{
/* Do not do anything in here so Outlook does not blame us
for a slow start. (See Screenshot in T6856 ) */
glob_hinst = hinst;
}
else if (reason == DLL_PROCESS_DETACH)
{
gpg_err_deinit (0);
}
return TRUE;
}
static char *
get_locale_dir (void)
{
char *instdir;
char *p;
char *dname;
instdir = get_gpg4win_dir();
if (!instdir)
return NULL;
/* Build the key: "<instdir>/share/locale". */
#define SLDIR "\\share\\locale"
dname = xmalloc (strlen (instdir) + strlen (SLDIR) + 1);
if (!dname)
{
xfree (instdir);
return NULL;
}
p = dname;
strcpy (p, instdir);
p += strlen (instdir);
strcpy (p, SLDIR);
xfree (instdir);
return dname;
}
static void
drop_locale_dir (char *locale_dir)
{
xfree (locale_dir);
}
static int
get_conf_bool (const char *name, int defaultVal)
{
char *val = NULL;
int ret;
load_extension_value (name, &val);
ret = val == NULL ? defaultVal : *val != '1' ? 0 : 1;
xfree (val);
return ret;
}
static int
dbg_compat (int oldval)
{
// We broke the debug levels at some point
// This is cmpatibility code with the old
// levels.
#define DBG_MEMORY_OLD (1<<5) // 32
#define DBG_MIME_PARSER_OLD (1<<7) // 128 Unified as DBG_DATA
#define DBG_MIME_DATA_OLD (1<<8) // 256 Unified in read_options
#define DBG_OOM_OLD (1<<9) // 512 Unified as DBG_OOM
#define DBG_OOM_EXTRA_OLD (1<<10)// 1024 Unified in read_options
int new_dbg = oldval;
if ((oldval & DBG_MEMORY_OLD))
{
new_dbg |= DBG_MEMORY;
new_dbg -= DBG_MEMORY_OLD;
}
if ((oldval & DBG_OOM_OLD))
{
new_dbg |= DBG_OOM;
new_dbg -= DBG_OOM_OLD;
}
if ((oldval & DBG_MIME_PARSER_OLD))
{
new_dbg |= DBG_DATA;
new_dbg -= DBG_MIME_PARSER_OLD;
}
if ((oldval & DBG_MIME_DATA_OLD))
{
new_dbg |= DBG_DATA;
new_dbg -= DBG_MIME_DATA_OLD;
}
if ((oldval & DBG_OOM_OLD))
{
new_dbg |= DBG_OOM;
new_dbg -= DBG_OOM_OLD;
}
if ((oldval & DBG_OOM_EXTRA_OLD))
{
new_dbg |= DBG_OOM;
new_dbg -= DBG_OOM_EXTRA_OLD;
}
#undef DBG_MEMORY_OLD
#undef DBG_MIME_PARSER_OLD
#undef DBG_MIME_DATA_OLD
#undef DBG_OOM_OLD
#undef DBG_OOM_EXTRA_OLD
return new_dbg;
}
/* Read option settings from the Registry. */
void
read_options (void)
{
char *val = NULL;
/* Set the log file first so that output from this function is
logged too. */
load_extension_value ("logFile", &val);
set_log_file (val);
xfree (val); val = NULL;
/* Parse the debug flags. */
load_extension_value ("enableDebug", &val);
opt.enable_debug = 0;
if (val)
{
char *p, *pend;
trim_spaces (val);
for (p = val; p; p = pend)
{
pend = strpbrk (p, ", \t\n\r\f");
if (pend)
{
*pend++ = 0;
pend += strspn (pend, ", \t\n\r\f");
}
if (isascii (*p) && isdigit (*p))
{
opt.enable_debug |= dbg_compat (strtoul (p, NULL, 0));
}
else if (!strcmp (p, "memory"))
opt.enable_debug |= DBG_MEMORY;
else if (!strcmp (p, "mime-parser"))
opt.enable_debug |= DBG_DATA;
else if (!strcmp (p, "mime-data"))
opt.enable_debug |= DBG_DATA;
else if (!strcmp (p, "oom"))
opt.enable_debug |= DBG_OOM;
else if (!strcmp (p, "oom-extra"))
opt.enable_debug |= DBG_OOM;
else
log_debug ("invalid debug flag `%s' ignored", p);
}
}
else
{
/* To help the user enable debugging make sure that the registry
key exists. Note that the other registry keys are stored
after using the configuration dialog. */
store_extension_value ("enableDebug", "0");
}
/* Yes we use free here because memtracing did not track the alloc
as the option for debuging was not read before. */
free (val); val = NULL;
if (opt.enable_debug)
log_debug ("enabled debug flags:%s%s%s%s\n",
(opt.enable_debug & DBG_MEMORY)? " memory":"",
(opt.enable_debug & DBG_DATA)? " data":"",
(opt.enable_debug & DBG_OOM)? " oom":"",
(opt.enable_debug & DBG_TRACE)? " trace":""
);
opt.enable_smime = get_conf_bool ("enableSmime", 0);
opt.encrypt_default = get_conf_bool ("encryptDefault", 0);
opt.sign_default = get_conf_bool ("signDefault", 0);
opt.inline_pgp = get_conf_bool ("inlinePGP", 0);
opt.reply_crypt = get_conf_bool ("replyCrypt", 1);
opt.prefer_smime = get_conf_bool ("preferSmime", 0);
opt.autoresolve = get_conf_bool ("autoresolve", 1);
opt.autoretrieve = get_conf_bool ("autoretrieve", 0);
opt.automation = get_conf_bool ("automation", 1);
opt.autosecure = get_conf_bool ("autosecure", 1);
opt.autotrust = get_conf_bool ("autotrust", 0);
opt.search_smime_servers = get_conf_bool ("searchSmimeServers", 0);
opt.smime_html_warn_shown = get_conf_bool ("smimeHtmlWarnShown", 0);
opt.smime_insecure_reply_fw_allowed = get_conf_bool ("smimeInsecureReplyAllowed", 0);
opt.auto_unstrusted = get_conf_bool ("autoencryptUntrusted", 0);
opt.autoimport = get_conf_bool ("autoimport", 0);
opt.splitBCCMails = get_conf_bool ("splitBCCMails", 0);
opt.combinedOpsEnabled = get_conf_bool ("combinedOpsEnabled", 0);
opt.encryptSubject = get_conf_bool ("encryptSubject", 0);
opt.noSaveBeforeDecrypt = get_conf_bool ("noSaveBeforeDecrypt", 0);
opt.closeOnUnknownWriteEvent = get_conf_bool ("closeOnUnknownWriteEvent", 0);
+ opt.disable_titus_handling = get_conf_bool("disableTitusHandling", 0);
if (!opt.automation)
{
// Disabling automation is a shorthand to disable the
// others, too.
opt.autosecure = 0;
opt.autoresolve = 0;
opt.autotrust = 0;
opt.autoretrieve = 0;
opt.autoimport = 0;
opt.auto_unstrusted = 0;
}
/* Draft encryption handling. */
if (get_conf_bool ("draftEnc", 0))
{
load_extension_value ("draftKey", &val);
if (val)
{
xfree (opt.draft_key);
opt.draft_key = val;
val = NULL;
}
}
else
{
xfree (opt.draft_key);
opt.draft_key = NULL;
}
opt.alwaysShowApproval = get_conf_bool ("alwaysShowApproval", 0);
/* Hidden options */
opt.sync_enc = 1; //get_conf_bool ("syncEnc", 0);
/* Due to an issue where async encryption would leave
unencrypted mails in the recently deleted folder on the
server we block it. */
opt.sync_dec = get_conf_bool ("syncDec", 0);
load_extension_value ("smimeNoCertSigErr", &val);
opt.smimeNoCertSigErr = val;
val = NULL;
}
/* Write current options back to the Registry. */
int
write_options (void)
{
struct
{
const char *name;
int mode;
int value;
char *s_val;
} table[] = {
{"smimeHtmlWarnShown", 0, opt.smime_html_warn_shown, NULL},
{"draftKey", 2, 0, opt.draft_key},
{NULL, 0, 0, NULL}
};
char buf[32];
int rc, i;
const char *string;
for (i=0; table[i].name; i++)
{
switch (table[i].mode)
{
case 0:
string = table[i].value? "1": "0";
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, string);
rc = store_extension_value (table[i].name, string);
break;
case 1:
sprintf (buf, "%d", table[i].value);
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, buf);
rc = store_extension_value (table[i].name, buf);
break;
case 2:
string = table[i].s_val? table[i].s_val : "";
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, string);
rc = store_extension_value (table[i].name, string);
break;
/* case 3: */
/* buf[0] = '0'; */
/* buf[1] = 0; */
/* switch (opt.default_protocol) */
/* { */
/* case PROTOCOL_UNKNOWN: buf[0] = '0'; /\* auto *\/ break; */
/* case PROTOCOL_OPENPGP: buf[0] = '1'; break; */
/* case PROTOCOL_SMIME: buf[0] = '2'; break; */
/* } */
/* log_debug ("storing option `%s' value=`%s'\n", */
/* table[i].name, buf); */
/* rc = store_extension_value (table[i].name, buf); */
/* break; */
case 4:
sprintf (buf, "0x%x", table[i].value);
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, buf);
rc = store_extension_value (table[i].name, buf);
break;
default:
rc = -1;
break;
}
if (rc)
log_error ("error storing option `%s': rc = %d\n", table[i].name, rc);
}
return 0;
}
diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp
index ef328db..1f03699 100644
--- a/src/mapihelp.cpp
+++ b/src/mapihelp.cpp
@@ -1,4209 +1,4210 @@
/* mapihelp.cpp - Helper functions for MAPI
* Copyright (C) 2005, 2007, 2008 g10 Code GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <windows.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "rfc822parse.h"
#include "mapihelp.h"
#include "parsetlv.h"
#include "oomhelp.h"
#include "cpphelp.h"
#include <string>
#include <sstream>
#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
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) )
{
log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
SRCNAME, func, hr);
result = 0;
}
else
result = (proparr->aulPropTag[0] & 0xFFFF0000);
if (proparr)
MAPIFreeBuffer (proparr);
TRETURN result;
}
/* Return the property tag for GpgOL Msg Class. */
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
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
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
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
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
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
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
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;
/* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */
GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0,
0x4F, 0xD6, 0x56, 0x85}};
wchar_t propname[] = L"Internet Charset Body";
int result;
memset (&mnid, 0, sizeof mnid);
mnid.lpguid = &guid;
mnid.ulKind = MNID_STRING;
mnid.Kind.lpwstrName = propname;
pmnid = &mnid;
hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr);
if (FAILED (hr))
proparr = NULL;
if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) )
{
log_debug ("%s:%s: can't get the Internet Charset Body property:"
" hr=%#lx\n", SRCNAME, __func__, hr);
result = -1;
}
else
{
result = 0;
*r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY);
}
if (proparr)
MAPIFreeBuffer (proparr);
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);
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;
/* {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))
{
pProps = NULL;
log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
SRCNAME, __func__, name, hr);
result = -1;
}
else
{
pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
pv.Value.lpszA = (char *)val;
hr = HrSetOneProp(msg, &pv);
if (hr)
{
log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
SRCNAME, __func__, name, hr);
result = -1;
}
else
result = 0;
}
if (pProps)
MAPIFreeBuffer (pProps);
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;
}
static int
mapi_set_transport_headers (LPMESSAGE message, const char *data)
{
TSTART;
if (!message)
{
STRANGEPOINT;
TRETURN -1;
}
HRESULT hr;
SPropValue prop;
prop.ulPropTag = PR_TRANSPORT_MESSAGE_HEADERS;
/* MAPI wants a non const value. So we better give it to avoid trouble */
prop.Value.lpszA = xstrdup (data);
log_data ("Setting transport headers to:\n%s", data);
hr = HrSetOneProp (message, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set transport headerrs property: hr=%#lx\n",
SRCNAME, __func__, hr);
TRETURN -1;
}
TRETURN 0;
}
int
mapi_set_content_type (LPMESSAGE message, const char *ct)
{
TSTART;
if (!message)
{
STRANGEPOINT;
TRETURN -1;
}
const auto headers = mapi_get_header (message);
if (headers.empty ())
{
log_err ("Failed to obtain original headers");
TRETURN -1;
}
std::stringstream newHeaders;
bool inContentHeader = false;
for (auto line: gpgol_split (headers, '\n'))
{
transform(line.begin(), line.end(), line.begin(), ::tolower);
if (starts_with (line, "content-type"))
{
inContentHeader = true;
if (ct)
{
newHeaders << "Content-type: " << ct << "\n";
}
}
else if (inContentHeader)
{
if (starts_with (line, " ") || starts_with (line, "\t"))
{
log_data ("Content header continuation %s ignored.", line.c_str());
continue;
}
else
{
inContentHeader = false;
}
}
else if (starts_with (line, "content-"))
{
inContentHeader = true;
continue;
}
else
{
newHeaders << line << "\n";
}
}
TRETURN mapi_set_transport_headers (message, newHeaders.str ().c_str ());
}
/* 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;
if (!nbytes)
{
log_dbg ("Empty body found");
gpgol_release (stream);
*r_nobody = 1;
TRETURN NULL;
}
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.
We check here whether the message is really encrypted by looking at
the object identifier inside the CMS data. Returns:
-1 := Unknown message type,
0 := The message is signed,
1 := The message is encrypted.
This function is required for two reasons:
1. Due to a bug in CryptoEx which sometimes assignes the *.CexEnc
message class to signed messages and only updates the message
class after accessing them. Thus in old stores there may be a
lot of *.CexEnc message which are actually just signed.
2. If the smime-type parameter is missing we need another way to
decide whether to decrypt or to verify.
3. Some messages lack a PR_TRANSPORT_MESSAGE_HEADERS and thus it is
not possible to deduce the message type from the mail headers.
This function may be used to identify the message anyway.
*/
static int
is_really_cms_encrypted (LPMESSAGE message)
{
HRESULT hr;
SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
LPMAPITABLE mapitable;
LPSRowSet mapirows;
unsigned int pos, n_attach;
int result = -1; /* Unknown. */
LPATTACH att = NULL;
LPSTREAM stream = NULL;
char buffer[24]; /* 24 bytes are more than enough to peek at.
Cf. ksba_cms_identify() from the libksba
package. */
const char *p;
ULONG nread;
size_t n;
tlvinfo_t ti;
hr = message->GetAttachmentTable (0, &mapitable);
if (FAILED (hr))
{
log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
SRCNAME, __func__, hr);
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,
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);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
SRCNAME, __func__, pos,
mapirows->aRow[pos].lpProps[0].Value.l, hr);
goto leave;
}
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
&& !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) )
{
log_dbg ("Failed to create msgclass tag. Mail might be read only.");
}
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'",
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 (!strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
{
/* IPM.Note.SMIME.MultipartSigned is set by Exchange for
any multipart/signed content type regardgless of the
protocol. So we need this handler even if SMIME is
disabled to check if it is a PGP Message.*/
newvalue = change_message_class_ipm_note_smime_multipartsigned
(message);
if (!newvalue && opt.enable_smime)
{
log_dbg ("multipart/signed mail is not a PGP/MIME mail");
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 (sync_override && have_override
&& !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
{
/* In case the original message class is not yet an GpgOL
class we set it here. This is needed to convince Outlook
not to do any special processing for IPM.Note.SMIME etc. */
LPSPropValue propval2 = NULL;
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
&propval2);
if (!SUCCEEDED (hr))
{
log_debug ("%s:%s: Failed to get PR_MESSAGE_CLASS_A property.",
SRCNAME, __func__);
}
else if (PROP_TYPE (propval2->ulPropTag) != PT_STRING8)
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is not string.",
SRCNAME, __func__);
}
else if (!propval2->Value.lpszA)
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is null.",
SRCNAME, __func__);
}
else if (!strcmp (propval2->Value.lpszA, s))
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is already the same.",
SRCNAME, __func__);
}
else
{
newvalue = (char*)xstrdup (s);
}
MAPIFreeBuffer (propval2);
}
else if (opt.enable_smime
&& (!strcmp (s, "IPM.Note.Secure.CexSig")
|| (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
{
newvalue = change_message_class_ipm_note_secure_cex
(message, cexenc);
}
if (r_type && !newvalue)
{
*r_type = string_to_type (s);
}
}
if (!newvalue)
{
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;
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;
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. */
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)
{
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)
{
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)
{
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))
{
log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
SRCNAME, __func__, filename, hr);
hr = -1;
}
else
{
hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
if (FAILED (hr))
{
log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
SRCNAME, __func__, hr);
stream->Revert ();
hr = -1;
}
else
{
stream->Commit (0);
hr = 0;
}
gpgol_release (stream);
}
gpgol_release (session);
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))
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-type of the first and only attachment of MESSAGE
or NULL if it does not exists. Caller must free. */
static std::string
get_first_attach_data_tag_fname (LPMESSAGE message, const char *mime_tag,
const char *file_name)
{
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);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
SRCNAME, __func__, pos,
mapirows->aRow[pos].lpProps[0].Value.l, hr);
goto leave;
}
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);
if (!result || strcmp(result, mime_tag))
{
log_debug ("%s:%s: wrong mime tag: %s", SRCNAME, __func__, result);
goto leave;
}
xfree (result);
result = get_attach_filename (att);
if (result && !strcmp(result, file_name))
{
FreeProws (mapirows);
gpgol_release (mapitable);
TRETURN mapi_get_first_attach_data(message);
}
log_debug ("%s:%s: wrong filename: %s", SRCNAME, __func__, result);
xfree(result);
leave:
if (att)
gpgol_release (att);
FreeProws (mapirows);
gpgol_release (mapitable);
TRETURN NULL;
}
/* 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;
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++)
{
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
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);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
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);
goto leave;
}
hr = att->SaveChanges (KEEP_OPEN_READWRITE);
if (hr)
{
log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
retval = 0;
leave:
gpgol_release (att);
TRETURN retval;
}
/* If the hidden property has not been set on ATTACH, set it and save
the changes. */
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)
&& 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);
goto leave;
}
hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
if (hr)
{
log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
retval = 0;
leave:
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
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);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
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.
Note that this function does not call SaveChanges. */
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);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
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
'P' = OpenPGP protocol
'X' = S/MIME protocol
'-' = don't care
If string is NULL, the property will get deleted.
Note that this function does not call SaveChanges. */
int
mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string)
{
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);
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);
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();
log_data ("Parsing header: %s", header_lines);
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;
}
if (rfc822parse_insert (msg, (const unsigned char*)header_lines, length) == -1)
{
log_dbg ("Failed to parse headers.");
rfc822parse_close (msg);
TRETURN nullptr;
}
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.
If the Content-Type multipart/mixed and the mail has a header starting
with X-Titus- we check for an matching attachment of containing the original
mail and return its content type*/
char *
get_content_type_from_header ( LPMESSAGE message, std::string hdrStr,
char **r_protocol, char **r_smtype)
{
TSTART
bool isWks = false;
const char *s;
char *retstr = 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, "multipart/mixed"))
+ if (!opt.disable_titus_handling
+ && !strcmp (retstr, "multipart/mixed"))
{
rfc822parse_field_t titus;
titus = rfc822parse_parse_field (msg, "X-Titus-*", -1);
if (titus)
{
std::string orig_mail_att;
orig_mail_att= get_first_attach_data_tag_fname
(message, "application/octet-stream", MIMEATTACHFILENAME);
rfc822parse_release_field (titus);
rfc822parse_release_field (ctx);
rfc822parse_close (msg);
xfree (retstr);
TRETURN get_content_type_from_header (message, orig_mail_att,
r_protocol, r_smtype);
}
}
else 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;
}
char *
mapi_get_message_content_type (LPMESSAGE message,
char **r_protocol, char **r_smtype)
{
TSTART;
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;
}
}
TRETURN get_content_type_from_header(message, hdrStr, r_protocol, r_smtype );
}
/* 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);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 25, 7:12 AM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
29/39/96630335d5101e467a60becfa30a
Attached To
rO GpgOL
Event Timeline
Log In to Comment