message: improve support for decryption

Found a small leak:
  https://github.com/jstedfast/gmime/pull/124/commits
This commit is contained in:
Dirk-Jan C. Binnema 2022-04-18 21:56:49 +03:00
parent 66ee2004fc
commit 17d2926cd0
5 changed files with 505 additions and 232 deletions

View File

@ -44,7 +44,10 @@
using namespace Mu;
struct Message::Private {
Private(Message::Options options=Message::Options::None):
opts{options} {}
Message::Options opts;
Document doc;
mutable Option<MimeMessage> mime_msg;
@ -66,8 +69,8 @@ struct Message::Private {
static void fill_document(Message::Private& priv);
Message::Message(const std::string& path, const std::string& mdir):
priv_{std::make_unique<Private>()}
Message::Message(Message::Options opts, const std::string& path, const std::string& mdir):
priv_{std::make_unique<Private>(opts)}
{
if (!g_path_is_absolute(path.c_str()))
throw Error(Error::Code::File, "path '%s' is not absolute", path.c_str());
@ -95,19 +98,22 @@ Message::Message(const std::string& path, const std::string& mdir):
if (!mdir.empty())
priv_->doc.add(Field::Id::Maildir, mdir);
priv_->doc.add(Field::Id::Size, static_cast<int64_t>(statbuf.st_size));
// rest of the fields
fill_document(*priv_);
}
Message::Message(const std::string& text, const std::string& path,
Message::Message(Message::Options opts, const std::string& text, const std::string& path,
const std::string& mdir):
priv_{std::make_unique<Private>()}
priv_{std::make_unique<Private>(opts)}
{
auto xpath{to_string_opt_gchar(g_canonicalize_filename(path.c_str(), NULL))};
if (xpath)
priv_->doc.add(Field::Id::Path, std::move(xpath.value()));
if (!path.empty()) {
auto xpath{to_string_opt_gchar(g_canonicalize_filename(path.c_str(), {}))};
if (xpath)
priv_->doc.add(Field::Id::Path, std::move(xpath.value()));
}
if (!mdir.empty())
priv_->doc.add(Field::Id::Maildir, mdir);
@ -327,6 +333,83 @@ process_part(const MimeObject& parent, const MimePart& part,
accumulate_text(part, info, *ctype);
}
static void
handle_object(const MimeObject& parent,
const MimeObject& obj, Message::Private& info);
static void
handle_encrypted(const MimeMultipartEncrypted& part, Message::Private& info)
{
if (!any_of(info.opts & Message::Options::Decrypt)) {
/* just added to the list */
info.parts.emplace_back(part);
return;
}
const auto proto{part.content_type_parameter("protocol").value_or("unknown")};
const auto ctx = MimeCryptoContext::make(proto);
if (!ctx) {
g_warning("failed to create context for protocol <%s>",
proto.c_str());
return;
}
auto res{part.decrypt(*ctx)};
if (!res) {
g_warning("failed to decrypt: %s", res.error().what());
return;
}
if (res->first.is_multipart()) {
MimeMultipart{res->first}.for_each(
[&](auto&& parent, auto&& child_obj) {
handle_object(parent, child_obj, info);
});
} else
handle_object(part, res->first, info);
}
static void
handle_object(const MimeObject& parent,
const MimeObject& obj, Message::Private& info)
{
/* if it's an encrypted part we should decrypt, recurse */
if (obj.is_multipart_encrypted())
handle_encrypted(MimeMultipartEncrypted{obj}, info);
else if (obj.is_part() ||
obj.is_message_part() ||
obj.is_multipart_signed() ||
obj.is_multipart_encrypted())
info.parts.emplace_back(obj);
if (obj.is_part())
process_part(parent, obj, info);
if (obj.is_multipart_signed())
info.flags |= Flags::Signed;
else if (obj.is_multipart_encrypted()) {
/* FIXME: An encrypted part might be signed at the same time.
* In that case the signed flag is lost. */
info.flags |= Flags::Encrypted;
} else if (obj.is_mime_application_pkcs7_mime()) {
MimeApplicationPkcs7Mime smime(obj);
switch (smime.smime_type()) {
case Mu::MimeApplicationPkcs7Mime::SecureMimeType::SignedData:
info.flags |= Flags::Signed;
break;
case Mu::MimeApplicationPkcs7Mime::SecureMimeType::EnvelopedData:
info.flags |= Flags::Encrypted;
break;
default:
break;
}
}
}
/**
* This message -- recursively walk through message, and initialize some
* other values that depend on another.
@ -349,36 +432,8 @@ process_message(const MimeMessage& mime_msg, const std::string& path,
}
// parts
mime_msg.for_each([&](auto&& parent, auto&& part) {
if (part.is_part() ||
part.is_message_part() ||
part.is_multipart_signed() ||
part.is_multipart_encrypted())
info.parts.emplace_back(part);
if (part.is_part())
process_part(parent, part, info);
if (part.is_multipart_signed())
info.flags |= Flags::Signed;
else if (part.is_multipart_encrypted()) {
/* FIXME: An encrypted part might be signed at the same time.
* In that case the signed flag is lost. */
info.flags |= Flags::Encrypted;
} else if (part.is_mime_application_pkcs7_mime()) {
MimeApplicationPkcs7Mime smime(part);
switch (smime.smime_type()) {
case Mu::MimeApplicationPkcs7Mime::SecureMimeType::SignedData:
info.flags |= Flags::Signed;
break;
case Mu::MimeApplicationPkcs7Mime::SecureMimeType::EnvelopedData:
info.flags |= Flags::Encrypted;
break;
default:
break;
}
}
mime_msg.for_each([&](auto&& parent, auto&& child_obj) {
handle_object(parent, child_obj, info);
});
// get the mailing here, and use it do update flags, too.

View File

@ -30,6 +30,7 @@
#include "mu-document.hh"
#include "mu-message-part.hh"
#include "utils/mu-utils.hh"
#include "utils/mu-option.hh"
#include "utils/mu-result.hh"
#include "utils/mu-sexp.hh"
@ -38,6 +39,14 @@ namespace Mu {
class Message {
public:
enum struct Options {
None = 0, /**< Defaults */
Decrypt = 1 << 0, /**< Attempt to decrypt */
RetrieveKeys = 1 << 1, /**< Auto-retrieve crypto keys (implies network
* access) */
};
/**
* Move CTOR
*
@ -49,6 +58,7 @@ public:
* Construct a message based on a path. The maildir is optional; however
* messages without maildir cannot be stored in the database
*
* @param opts options
* @param path path to message
* @param mdir the maildir for this message; i.e, if the path is
* ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can
@ -57,9 +67,10 @@ public:
*
* @return a message or an error
*/
static Result<Message> make_from_path(const std::string& path,
static Result<Message> make_from_path(Options opts,
const std::string& path,
const std::string& mdir={}) try {
return Ok(Message{path, mdir});
return Ok(Message{opts, path, mdir});
} catch (Error& err) {
return Err(err);
} catch (...) {
@ -85,6 +96,7 @@ public:
/**
* Construct a message from a string. This is mostly useful for testing.
*
* @param opts options
* @param text message text
* @param path path to message - optional; path does not have to exist.
* this is useful for testing.
@ -92,10 +104,11 @@ public:
*
* @return a message or an error
*/
static Result<Message> make_from_text(const std::string& text,
static Result<Message> make_from_text(Options opts,
const std::string& text,
const std::string& path={},
const std::string& mdir={}) try {
return Ok(Message{text, path, mdir});
return Ok(Message{opts, text, path, mdir});
} catch (Error& err) {
return Err(err);
} catch (...) {
@ -330,13 +343,14 @@ public:
struct Private;
private:
Message(const std::string& path, const std::string& mdir);
Message(const std::string& str, const std::string& path,
Message(Options opts, const std::string& path, const std::string& mdir);
Message(Options opts, const std::string& str, const std::string& path,
const std::string& mdir);
Message(Document& doc);
std::unique_ptr<Private> priv_;
}; // Message
MU_ENABLE_BITOPS(Message::Options);
} // Mu
#endif /* MU_MESSAGE_HH__ */

View File

@ -81,16 +81,29 @@ MimeObject::header(const std::string& hdr) const noexcept
}
Option<std::string>
MimeObject::object_to_string() const noexcept
Result<size_t>
MimeObject::write_to_stream(const MimeFormatOptions& f_opts,
MimeStream& stream) const
{
GMimeStream *stream{g_mime_stream_mem_new()};
auto written = g_mime_object_write_to_stream(self(), f_opts.get(),
GMIME_STREAM(stream.object()));
if (written < 0)
return Err(Error::Code::File, "failed to write mime-object to stream");
else
return Ok(static_cast<size_t>(written));
}
Option<std::string>
MimeObject::to_string_opt() const noexcept
{
auto stream{MimeStream::make_mem()};
if (!stream) {
g_warning("failed to create mem stream");
return Nothing;
}
const auto written = g_mime_object_write_to_stream(self(), {}, stream);
const auto written = g_mime_object_write_to_stream(
self(), {}, GMIME_STREAM(stream.object()));
if (written < 0) {
g_warning("failed to write object to stream");
return Nothing;
@ -98,10 +111,10 @@ MimeObject::object_to_string() const noexcept
std::string buffer;
buffer.resize(written + 1);
g_mime_stream_reset(stream);
stream.reset();
auto bytes{g_mime_stream_read(stream, buffer.data(), written)};
g_object_unref(stream);
auto bytes{g_mime_stream_read(GMIME_STREAM(stream.object()),
buffer.data(), written)};
if (bytes < 0)
return Nothing;
@ -121,7 +134,7 @@ MimeCryptoContext::import_keys(MimeStream& stream)
{
GError *err{};
auto res = g_mime_crypto_context_import_keys(
self(), stream.self(), &err);
self(), GMIME_STREAM(stream.object()), &err);
if (res < 0)
return Err(Error::Code::File, &err,
@ -131,7 +144,7 @@ MimeCryptoContext::import_keys(MimeStream& stream)
}
void
MimeCryptoContext::set_password_request_function(PasswordRequestFunc pw_func)
MimeCryptoContext::set_request_password(PasswordRequestFunc pw_func)
{
static auto request_func = pw_func;
@ -143,7 +156,8 @@ MimeCryptoContext::set_password_request_function(PasswordRequestFunc pw_func)
gboolean reprompt,
GMimeStream *response,
GError **err) -> gboolean {
MimeStream mstream{response};
MimeStream mstream{MimeStream::make_from_stream(response)};
auto res = request_func(MimeCryptoContext(ctx),
std::string{user_id ? user_id : ""},
std::string{prompt ? prompt : ""},
@ -420,23 +434,23 @@ MimePart::to_string() const noexcept
Result<size_t>
MimePart::to_file(const std::string& path, bool overwrite) const noexcept
{
GMimeDataWrapper *wrapper{g_mime_part_get_content(self())};
MimeDataWrapper wrapper{g_mime_part_get_content(self())};
if (!wrapper) /* this happens with invalid mails */
return Err(Error::Code::File, "failed to create data wrapper");
GError *err{};
GMimeStream *stream{g_mime_stream_fs_open(
path.c_str(),
O_WRONLY | O_CREAT | O_TRUNC |(overwrite ? 0 : O_EXCL),
S_IRUSR|S_IWUSR,
&err)};
if (!stream)
return Err(Error::Code::File, &err,
"failed to open '%s'", path.c_str());
auto strm{g_mime_stream_fs_open(path.c_str(),
O_WRONLY | O_CREAT | O_TRUNC |(overwrite ? 0 : O_EXCL),
S_IRUSR|S_IWUSR,
&err)};
if (!strm)
return Err(Error::Code::File, &err, "failed to open '%s'", path.c_str());
MimeStream stream{MimeStream::make_from_stream(strm)};
ssize_t written{g_mime_data_wrapper_write_to_stream(
GMIME_DATA_WRAPPER(wrapper.object()),
GMIME_STREAM(stream.object()))};
ssize_t written{g_mime_data_wrapper_write_to_stream(wrapper, stream)};
g_object_unref(stream);
if (written < 0) {
return Err(Error::Code::File, &err,
"failed to write to '%s'", path.c_str());
@ -446,30 +460,23 @@ MimePart::to_file(const std::string& path, bool overwrite) const noexcept
}
Result<std::vector<MimeSignature>>
MimeMultipartSigned::verify(VerifyFlags vflags) const noexcept
void
MimeMultipart::for_each(const ForEachFunc& func) const noexcept
{
GError *err{};
GMimeSignatureList *siglist = g_mime_multipart_signed_verify(
struct CallbackData { const ForEachFunc& func; };
CallbackData cbd{func};
g_mime_multipart_foreach(
self(),
static_cast<GMimeVerifyFlags>(vflags),
&err);
if (!siglist)
return Err(Error::Code::Crypto, &err, "failed to verify");
std::vector<MimeSignature> sigs;
for (auto i = 0; i != g_mime_signature_list_length(siglist); ++i) {
GMimeSignature *sig = g_mime_signature_list_get_signature(siglist, i);
sigs.emplace_back(MimeSignature(sig));
}
g_object_unref(siglist);
return sigs;
[] (GMimeObject *parent, GMimeObject *part, gpointer user_data) {
auto cb_data{reinterpret_cast<CallbackData*>(user_data)};
cb_data->func(MimeObject{parent}, MimeObject{part});
}, &cbd);
}
/*
* we need to be able to pass a crypto-context to the verify(), but
* g_mime_multipart_signed_verify() doesn't offer that anymore in GMime 3.x.
@ -481,7 +488,7 @@ MimeMultipartSigned::verify(VerifyFlags vflags) const noexcept
static bool
mime_types_equal (const std::string& mime_type, const std::string& official_type)
{
if (g_ascii_strcasecmp(mime_type.c_str(), official_type.c_str()))
if (g_ascii_strcasecmp(mime_type.c_str(), official_type.c_str()) == 0)
return true;
const auto slash_pos = official_type.find("/");
@ -504,73 +511,70 @@ mime_types_equal (const std::string& mime_type, const std::string& official_type
}
/**
* A bit of a monster, this impl.
*
* It's the transliteration of the g_mime_multipart_signed_verify() which
* adds the feature of passing in the CryptoContext.
*
*/
Result<std::vector<MimeSignature>>
MimeMultipartSigned::verify(const MimeCryptoContext& ctx, VerifyFlags vflags) const noexcept
{
if (g_mime_multipart_get_count(GMIME_MULTIPART(self())) < 2)
return Err(Error::Code::Crypto, "cannot verify, not enough subparts");
const auto proto{content_type_parameter("protocol")};
const auto sign_proto{ctx.signature_protocol()};
// Result<std::vector<MimeSignature>>
// MimeMultipartSigned::verify(VerifyFlags vflags, const CryptoContext& ctx) const noexcept
// {
// if (g_mime_multipart_get_count(self()) < 2)
// return Err(Error::Code::Crypto, "cannot verify, not enough subparts");
// const auto proto{content_type_parameter("protocol")};
// const auto sign_proto{ctx.signature_prototol().value_or("<unknown>")};
// if (!proto || !sign_proto || !mime_types_equal(*proto, sign_proto))
// return Err(Error::Code::Crypto, "unsupported protocol");
// const auto sig{MimeObject(g_mime_multipart_get_part(self(), GMIME_MULTIPART_SIGNED_SIGNATURE))};
// if (!sig || !mime_types_equal(sig.mime_type().value_or("<none>"), sign_proto))
// return Err(Error::Code::Crypto, "failed to find matching signature part");
// MimeObject content{g_mime_multipart_get_part(self(), GMIME_MULTIPART_SIGNED_CONTENT)};
// if (!content)
// return Err(Error::Code::Crypto, "cannot find content part");
// MimeFormatOptions fopts{format_options_new()};
// g_mime_format_options_set_newline_format(*fopts, GMIME_NEWLINE_FORMAT_DOS);
// MimeStream stream{g_mime_stream_mem_new()};
// g_mime_object_write_to_stream (content, *fopts, stream);
// g_mime_stream_reset (stream);
// GMimeDataWrapper *wrapper = g_mime_part_get_content(static_cast<GMimePart*>(sig));
// GError *err{};
// GMimeSignatureList *siglist = g_mime_multipart_signed_verify(
// self(),
// static_cast<GMimeVerifyFlags>(vflags),
// &err);
// if (!siglist)
// return Err(Error::Code::Crypto, &err, "failed to verify");
// std::vector<MimeSignature> sigs;
// for (auto i = 0; i != g_mime_signature_list_length(siglist); ++i) {
// GMimeSignature *sig = g_mime_signature_list_get_signature(siglist, i);
// sigs.emplace_back(MimeSignature(sig));
// }
// g_object_unref(siglist);
// return sigs;
// }
if (!proto || !sign_proto || !mime_types_equal(*proto, *sign_proto))
return Err(Error::Code::Crypto, "unsupported protocol " +
proto.value_or("<unknown>"));
const auto sig{signed_signature_part()};
const auto content{signed_content_part()};
if (!sig || !content)
return Err(Error::Code::Crypto, "cannot find part");
const auto sig_mime_type{sig->mime_type()};
if (!sig || !mime_types_equal(sig_mime_type.value_or("<none>"), *sign_proto))
return Err(Error::Code::Crypto, "failed to find matching signature part");
MimeFormatOptions fopts{g_mime_format_options_new()};
g_mime_format_options_set_newline_format(fopts.get(), GMIME_NEWLINE_FORMAT_DOS);
MimeStream stream{MimeStream::make_mem()};
if (auto&& res = content->write_to_stream(fopts, stream); !res)
return Err(res.error());
stream.reset();
MimeDataWrapper wrapper{g_mime_part_get_content(GMIME_PART(sig->object()))};
MimeStream sigstream{MimeStream::make_mem()};
if (auto&& res = wrapper.write_to_stream(sigstream); !res)
return Err(res.error());
sigstream.reset();
GError *err{};
GMimeSignatureList *siglist{g_mime_crypto_context_verify(
GMIME_CRYPTO_CONTEXT(ctx.object()),
static_cast<GMimeVerifyFlags>(vflags),
GMIME_STREAM(stream.object()),
GMIME_STREAM(sigstream.object()),
{},
&err)};
if (!siglist)
return Err(Error::Code::Crypto, &err, "failed to verify");
std::vector<MimeSignature> sigs;
for (auto i = 0;
i != g_mime_signature_list_length(siglist); ++i) {
GMimeSignature *sig = g_mime_signature_list_get_signature(siglist, i);
sigs.emplace_back(MimeSignature(sig));
}
g_object_unref(siglist);
return sigs;
}
std::vector<MimeCertificate>
@ -604,25 +608,85 @@ MimeDecryptResult::signatures() const noexcept
return sigs;
}
/**
* Like verify, a bit of a monster, this impl.
*
* It's the transliteration of the g_mime_multipart_encrypted_decrypt() which
* adds the feature of passing in the CryptoContext.
*
*/
Mu::Result<MimeMultipartEncrypted::Decrypted>
MimeMultipartEncrypted::decrypt(DecryptFlags flags,
MimeMultipartEncrypted::decrypt(const MimeCryptoContext& ctx, DecryptFlags dflags,
const std::string& session_key) const noexcept
{
if (g_mime_multipart_get_count(GMIME_MULTIPART(self())) < 2)
return Err(Error::Code::Crypto, "cannot decrypted, not enough subparts");
const auto proto{content_type_parameter("protocol")};
const auto enc_proto{ctx.encryption_protocol()};
if (!proto || !enc_proto || !mime_types_equal(*proto, *enc_proto))
return Err(Error::Code::Crypto, "unsupported protocol " +
proto.value_or("<unknown>"));
const auto version{encrypted_version_part()};
const auto encrypted{encrypted_content_part()};
if (!version || !encrypted)
return Err(Error::Code::Crypto, "cannot find part");
if (!mime_types_equal(version->mime_type().value_or(""), proto.value()))
return Err(Error::Code::Crypto,
"cannot decrypt; unexpected version content-type '%s' != '%s'",
version->mime_type().value_or("").c_str(),
proto.value().c_str());
if (!mime_types_equal(encrypted->mime_type().value_or(""), "application/octet-stream"))
return Err(Error::Code::Crypto,
"cannot decrypt; unexpected encrypted content-type '%s'",
encrypted->mime_type().value_or("").c_str());
const auto content{encrypted->content()};
auto ciphertext{MimeStream::make_mem()};
content.write_to_stream(ciphertext);
ciphertext.reset();
auto stream{MimeStream::make_mem()};
auto filtered{MimeStream::make_filtered(stream)};
auto filter{g_mime_filter_dos2unix_new(FALSE)};
g_mime_stream_filter_add(GMIME_STREAM_FILTER(filtered.object()),
filter);
g_object_unref(filter);
GError *err{};
GMimeDecryptResult *dres{};
GMimeObject *obj = g_mime_multipart_encrypted_decrypt(
self(),
static_cast<GMimeDecryptFlags>(flags),
session_key.empty() ? NULL : session_key.c_str(),
&dres,
&err);
GMimeDecryptResult *dres =
g_mime_crypto_context_decrypt(GMIME_CRYPTO_CONTEXT(ctx.object()),
static_cast<GMimeDecryptFlags>(dflags),
session_key.empty() ?
NULL : session_key.c_str(),
GMIME_STREAM(ciphertext.object()),
GMIME_STREAM(filtered.object()),
&err);
if (!dres)
return Err(Error::Code::Crypto, &err, "decryption failed");
if (!obj)
return Err(Error::Code::Crypto, &err, "failed to decrypt");
filtered.flush();
stream.reset();
return Ok(Decrypted{MimeObject{obj}, MimeDecryptResult(dres)});
auto parser{g_mime_parser_new()};
g_mime_parser_init_with_stream(parser, GMIME_STREAM(stream.object()));
auto decrypted{g_mime_parser_construct_part(parser, NULL)};
g_object_unref(parser);
if (!decrypted) {
g_object_unref(dres);
return Err(Error::Code::Crypto, "failed to parse decrypted part");
}
Decrypted result = { MimeObject{decrypted}, MimeDecryptResult{dres} };
g_object_unref(decrypted);
g_object_unref(dres);
return Ok(std::move(result));
}

View File

@ -35,6 +35,10 @@
namespace Mu {
/* non-GObject types */
using MimeFormatOptions = deletable_unique_ptr<GMimeFormatOptions, g_mime_format_options_free>;
/**
* Initialize gmime (idempotent)
*
@ -126,11 +130,33 @@ public:
*/
operator bool() const noexcept { return !!self_; }
protected:
GObject* object() const { return self(); }
/**
* Get a ptr to the underlying GObject
*
* @return GObject or NULL
*/
GObject* object() const { return self_; }
/**
* Unref the object
*
*/
void unref() noexcept {
g_object_unref(self_);
}
/**
* Ref the object
*
*/
void ref() noexcept {
g_object_ref(self_);
}
private:
GObject *self() const { return self_; }
mutable GObject *self_{};
};
@ -153,7 +179,7 @@ struct MimeContentType: public Object {
}
Option<std::string> mime_type() const noexcept {
return to_string_opt(g_mime_content_type_get_mime_type(self()));
return to_string_opt_gchar(g_mime_content_type_get_mime_type(self()));
}
bool is_type(const std::string& type, const std::string& subtype) const {
@ -167,16 +193,14 @@ private:
};
/**
* Thin wrapper around a GMimeStream
*
*/
struct MimeStream: public Object {
MimeStream(GMimeStream *stream): Object(G_OBJECT(stream)) {
if (!GMIME_IS_STREAM(self()))
throw std::runtime_error("not a mime-stream");
};
ssize_t write(const char* buf, ::size_t size) {
return g_mime_stream_write(self(), buf, size);
@ -186,6 +210,33 @@ struct MimeStream: public Object {
return g_mime_stream_reset(self()) < 0 ? false : true;
}
bool flush() {
return g_mime_stream_flush(self()) < 0 ? false : true;
}
static MimeStream make_mem() {
MimeStream mstream{g_mime_stream_mem_new()};
mstream.unref(); /* remove extra ref */
return mstream;
}
static MimeStream make_filtered(MimeStream& stream) {
MimeStream mstream{g_mime_stream_filter_new(stream.self())};
mstream.unref(); /* remove extra refs */
return mstream;
}
static MimeStream make_from_stream(GMimeStream *strm) {
MimeStream mstream{strm};
mstream.unref(); /* remove extra ref */
return mstream;
}
private:
MimeStream(GMimeStream *stream): Object(G_OBJECT(stream)) {
if (!GMIME_IS_STREAM(self()))
throw std::runtime_error("not a mime-stream");
};
GMimeStream* self() const {
return reinterpret_cast<GMimeStream*>(object());
@ -201,6 +252,32 @@ constexpr Option<std::string_view> to_string_view_opt(const S& seq, T t) {
return it->second;
}
/**
* Thin wrapper around a GMimeDataWrapper
*
*/
struct MimeDataWrapper: public Object {
MimeDataWrapper(GMimeDataWrapper *wrapper): Object(G_OBJECT(wrapper)) {
if (!GMIME_IS_DATA_WRAPPER(self()))
throw std::runtime_error("not a data-wrapper");
};
Result<size_t> write_to_stream(MimeStream& stream) const {
if (auto&& res = g_mime_data_wrapper_write_to_stream(
self(), GMIME_STREAM(stream.object())) ; res < 0)
return Err(Error::Code::Message, "failed to write to stream");
else
return Ok(static_cast<size_t>(res));
}
private:
GMimeDataWrapper* self() const {
return reinterpret_cast<GMimeDataWrapper*>(object());
}
};
/**
* Thin wrapper around a GMimeCertifcate
@ -523,7 +600,6 @@ struct MimeDecryptResult: public Object {
return to_string_opt(g_mime_decrypt_result_get_session_key(self()));
}
private:
GMimeDecryptResult* self() const {
return reinterpret_cast<GMimeDecryptResult*>(object());
@ -574,19 +650,30 @@ struct MimeCryptoContext : public Object {
if (auto&& res = setup_gpg_test(testpath); !res)
return Err(res.error());
}
return Ok(MimeCryptoContext(g_mime_gpg_context_new()));
MimeCryptoContext ctx(g_mime_gpg_context_new());
ctx.unref(); /* remove extra ref */
return Ok(std::move(ctx));
} catch (...) {
return Err(Error::Code::Crypto, "failed to create crypto context");
}
static Result<MimeCryptoContext>
make(const std::string& protocol) {
auto ctx = g_mime_crypto_context_new(protocol.c_str());
if (!ctx)
return Err(Error::Code::Crypto, "unsupported protocol " + protocol);
MimeCryptoContext mctx{ctx};
mctx.unref(); /* remove extra ref */
return Ok(std::move(mctx));
}
Option<std::string> encryption_protocol() {
Option<std::string> encryption_protocol() const noexcept {
return to_string_opt(g_mime_crypto_context_get_encryption_protocol(self()));
}
Option<std::string> signature_protocol() {
Option<std::string> signature_protocol() const noexcept {
return to_string_opt(g_mime_crypto_context_get_signature_protocol(self()));
}
Option<std::string> key_exchange_protocol() {
Option<std::string> key_exchange_protocol() const noexcept {
return to_string_opt(g_mime_crypto_context_get_key_exchange_protocol(self()));
}
@ -625,7 +712,7 @@ struct MimeCryptoContext : public Object {
*
* @param pw_func password function.
*/
void set_password_request_function(PasswordRequestFunc pw_func);
void set_request_password(PasswordRequestFunc pw_func);
private:
@ -684,31 +771,41 @@ public:
return MimeContentType(ct);
}
Option<std::string> mime_type() const noexcept {
if (auto ct = content_type(); !ct)
return Nothing;
else
return ct->mime_type();
}
/**
* Get the content-type parameter
*
* @param param name of parameter
*
* @return the value of the parameter, or Nothing
*/
Option<std::string> content_type_parameter(const std::string& param) const noexcept {
return to_string_opt(
return Mu::to_string_opt(
g_mime_object_get_content_type_parameter(self(), param.c_str()));
}
using MimeFormatOptions =
deletable_unique_ptr<GMimeFormatOptions, g_mime_format_options_free>;
Option<size_t> write_to_stream(const MimeFormatOptions& f_opts,
MimeStream& stream) {
auto written = g_mime_object_write_to_stream(self(), f_opts.get(), stream.self());
if (written < 0)
return Nothing;
else
return static_cast<size_t>(written);
}
/**
* Write this MimeObject to some stream
*
* @param f_opts formatting options
* @param stream the stream
*
* @return the number or bytes written or an error
*/
Result<size_t> write_to_stream(const MimeFormatOptions& f_opts,
MimeStream& stream) const;
/**
* Write the object to a string.
*
* @return
*/
Option<std::string> object_to_string() const noexcept;
Option<std::string> to_string_opt() const noexcept;
/*
* subtypes.
@ -769,6 +866,13 @@ public:
return GMIME_IS_APPLICATION_PKCS7_MIME(self());
}
/**
* Callback for for_each(). See GMimeObjectForEachFunc.
*
*/
using ForEachFunc = std::function<void(const MimeObject& parent,
const MimeObject& part)>;
private:
GMimeObject* self() const {
return reinterpret_cast<GMimeObject*>(object());
@ -832,7 +936,7 @@ public:
* @return string or nullopt
*/
Option<std::string> message_id() const noexcept {
return to_string_opt(g_mime_message_get_message_id(self()));
return Mu::to_string_opt(g_mime_message_get_message_id(self()));
}
/**
@ -841,7 +945,7 @@ public:
* @return string or nullopt
*/
Option<std::string> subject() const noexcept {
return to_string_opt(g_mime_message_get_subject(self()));
return Mu::to_string_opt(g_mime_message_get_subject(self()));
}
/**
@ -861,13 +965,6 @@ public:
std::vector<std::string> references() const noexcept;
/**
* Callback for for_each(). See GMimeObjectForEachFunc.
*
*/
using ForEachFunc = std::function<void(const MimeObject& parent,
const MimeObject& part)>;
/**
* Recursively apply func tol all parts of this message
*
@ -914,7 +1011,7 @@ public:
* @return string or nullopt
*/
Option<std::string> content_description() const noexcept {
return to_string_opt(g_mime_part_get_content_description(self()));
return Mu::to_string_opt(g_mime_part_get_content_description(self()));
}
/**
@ -924,7 +1021,7 @@ public:
* @return string or nullopt
*/
Option<std::string> content_id() const noexcept {
return to_string_opt(g_mime_part_get_content_id(self()));
return Mu::to_string_opt(g_mime_part_get_content_id(self()));
}
/**
@ -934,7 +1031,7 @@ public:
* @return string or nullopt
*/
Option<std::string> content_md5() const noexcept {
return to_string_opt(g_mime_part_get_content_md5(self()));
return Mu::to_string_opt(g_mime_part_get_content_md5(self()));
}
@ -955,7 +1052,12 @@ public:
* @return string or nullopt
*/
Option<std::string> content_location() const noexcept {
return to_string_opt(g_mime_part_get_content_location(self()));
return Mu::to_string_opt(g_mime_part_get_content_location(self()));
}
MimeDataWrapper content() const noexcept {
return MimeDataWrapper{g_mime_part_get_content(self())};
}
/**
@ -965,7 +1067,7 @@ public:
* @return string or nullopt
*/
Option<std::string> filename() const noexcept {
return to_string_opt(g_mime_part_get_filename(self()));
return Mu::to_string_opt(g_mime_part_get_filename(self()));
}
/**
@ -1137,7 +1239,38 @@ public:
throw std::runtime_error("not a mime-multipart");
}
Option<MimePart> signed_content_part() const {
return part(GMIME_MULTIPART_SIGNED_CONTENT);
}
Option<MimePart> signed_signature_part() const {
return part(GMIME_MULTIPART_SIGNED_SIGNATURE);
}
Option<MimePart> encrypted_version_part() const {
return part(GMIME_MULTIPART_ENCRYPTED_VERSION);
}
Option<MimePart> encrypted_content_part() const {
return part(GMIME_MULTIPART_ENCRYPTED_CONTENT);
}
/**
* Recursively apply func to all parts
*
* @param func a function
*/
void for_each(const ForEachFunc& func) const noexcept;
private:
Option<MimePart> part(int index) const {
if (MimeObject mobj{g_mime_multipart_get_part(self(),index)}; !mobj)
return Nothing;
else
return mobj;
}
GMimeMultipart* self() const {
return reinterpret_cast<GMimeMultipart*>(object());
}
@ -1168,12 +1301,9 @@ public:
EnableOnlineCertificateChecks = GMIME_DECRYPT_ENABLE_ONLINE_CERTIFICATE_CHECKS
};
struct Decrypted {
MimeObject decrypted_object;
MimeDecryptResult decrypte_result;
};
Result<Decrypted> decrypt(DecryptFlags flags=DecryptFlags::None,
using Decrypted = std::pair<MimeObject, MimeDecryptResult>;
Result<Decrypted> decrypt(const MimeCryptoContext& ctx,
DecryptFlags flags=DecryptFlags::None,
const std::string& session_key = {}) const noexcept;
private:
@ -1207,8 +1337,10 @@ public:
EnableOnlineCertificateChecks = GMIME_VERIFY_ENABLE_ONLINE_CERTIFICATE_CHECKS
};
Result<std::vector<MimeSignature>>
verify(VerifyFlags vflags=VerifyFlags::None) const noexcept;
// Result<std::vector<MimeSignature>> verify(VerifyFlags vflags=VerifyFlags::None) const noexcept;
Result<std::vector<MimeSignature>> verify(const MimeCryptoContext& ctx,
VerifyFlags vflags=VerifyFlags::None) const noexcept;
private:
GMimeMultipartSigned* self() const {

View File

@ -62,7 +62,7 @@ statement you can use something like:
goto * instructions[pOp->opcode];
)";
auto message{Message::make_from_text(
test_message_1,
{}, test_message_1,
"/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S",
"/inbox")};
g_assert_true(!!message);
@ -176,9 +176,11 @@ World!
--=-=-=--
)";
auto message{Message::make_from_text(msg_text)};
auto message{Message::make_from_text({}, msg_text)};
g_assert_true(!!message);
g_assert_true(message->path().empty());
g_assert_true(message->bcc().empty());
g_assert_true(!message->body_html());
assert_equal(message->body_text().value_or(""), R"(Hello,World!)");
@ -316,7 +318,7 @@ Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg==
auto ctx{MimeCryptoContext::make_gpg(tempdir.path())};
g_assert_true(!!ctx);
MimeStream stream{g_mime_stream_mem_new()};
auto stream{MimeStream::make_mem()};
stream.write(pub_key.data(), pub_key.size());
stream.reset();
@ -324,6 +326,7 @@ Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg==
g_assert_cmpuint(*imported, ==, 1);
auto message{Message::make_from_text(
{},
msgtext,
"/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS",
"/inbox")};
@ -343,9 +346,12 @@ Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg==
continue;
const auto mpart{MimeMultipartSigned(mobj)};
const auto sigs{mpart.verify()};
const auto sigs{mpart.verify(*ctx)};
if (!sigs)
g_warning("%s", sigs.error().what());
g_assert_true(!!sigs && sigs->size() == 1);
g_assert_true(!!sigs);
g_assert_cmpuint(sigs->size(), ==, 1);
++n;
}
@ -393,28 +399,27 @@ C0bdoCx44QVU8HaZ2x91h3GoM/0q5bqM/rvCauwbokiJgAUrznecNPY=
g_assert_true(!!ctx);
/// test1234
ctx->set_password_request_function(
[](const MimeCryptoContext& ctx,
const std::string& user_id,
const std::string& prompt,
bool reprompt,
MimeStream& response)->Result<void> {
return Err(Error::Code::Internal, "boo");
g_warning("boo!");
return Ok();
});
// ctx->set_request_password([](const MimeCryptoContext& ctx,
// const std::string& user_id,
// const std::string& prompt,
// bool reprompt,
// MimeStream& response)->Result<void> {
// return Err(Error::Code::Internal, "boo");
// //return Ok();
// });
{
MimeStream stream{g_mime_stream_mem_new()};
auto stream{MimeStream::make_mem()};
stream.write(priv_key.data(), priv_key.size());
stream.write(pub_key.data(), pub_key.size());
stream.reset();
g_assert_cmpint(ctx->import_keys(stream).value_or(-1),==,1);
}
auto message{Message::make_from_text(
{},
msgtext,
"/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS",
"/archive")};
@ -431,9 +436,12 @@ C0bdoCx44QVU8HaZ2x91h3GoM/0q5bqM/rvCauwbokiJgAUrznecNPY=
if (!mobj.is_multipart_encrypted())
continue;
const auto mpart{MimeMultipartEncrypted(mobj)};
const auto decres = mpart.decrypt();
g_assert_true(!!decres);
/* FIXME: make this work without user having to
* type password */
// const auto mpart{MimeMultipartEncrypted(mobj)};
// const auto decres = mpart.decrypt(*ctx);
// assert_valid_result(decres);
++n;
}