diff --git a/lib/message/mu-message.cc b/lib/message/mu-message.cc index caa3750c..69d1b942 100644 --- a/lib/message/mu-message.cc +++ b/lib/message/mu-message.cc @@ -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 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()} +Message::Message(Message::Options opts, const std::string& path, const std::string& mdir): + priv_{std::make_unique(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(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()} + priv_{std::make_unique(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. diff --git a/lib/message/mu-message.hh b/lib/message/mu-message.hh index e61ee895..9ac49086 100644 --- a/lib/message/mu-message.hh +++ b/lib/message/mu-message.hh @@ -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 make_from_path(const std::string& path, + static Result 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 make_from_text(const std::string& text, + static Result 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 priv_; }; // Message +MU_ENABLE_BITOPS(Message::Options); + } // Mu #endif /* MU_MESSAGE_HH__ */ diff --git a/lib/message/mu-mime-object.cc b/lib/message/mu-mime-object.cc index 830203dc..fdb07fdb 100644 --- a/lib/message/mu-mime-object.cc +++ b/lib/message/mu-mime-object.cc @@ -81,16 +81,29 @@ MimeObject::header(const std::string& hdr) const noexcept } -Option -MimeObject::object_to_string() const noexcept +Result +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(written)); +} + +Option +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 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> -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(vflags), - &err); - - if (!siglist) - return Err(Error::Code::Crypto, &err, "failed to verify"); - - std::vector 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(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> +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> -// 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("")}; - -// 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(""), 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(sig)); - - - - -// GError *err{}; -// GMimeSignatureList *siglist = g_mime_multipart_signed_verify( -// self(), -// static_cast(vflags), -// &err); - -// if (!siglist) -// return Err(Error::Code::Crypto, &err, "failed to verify"); - -// std::vector 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("")); + 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(""), *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(vflags), + GMIME_STREAM(stream.object()), + GMIME_STREAM(sigstream.object()), + {}, + &err)}; + if (!siglist) + return Err(Error::Code::Crypto, &err, "failed to verify"); + std::vector 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 @@ -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::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("")); + + 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(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(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)); } diff --git a/lib/message/mu-mime-object.hh b/lib/message/mu-mime-object.hh index 43818396..7e260a10 100644 --- a/lib/message/mu-mime-object.hh +++ b/lib/message/mu-mime-object.hh @@ -35,6 +35,10 @@ namespace Mu { +/* non-GObject types */ + +using MimeFormatOptions = deletable_unique_ptr; + /** * 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 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(object()); @@ -201,6 +252,32 @@ constexpr Option 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 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(res)); + } + +private: + GMimeDataWrapper* self() const { + return reinterpret_cast(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(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 + 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 encryption_protocol() { + Option encryption_protocol() const noexcept { return to_string_opt(g_mime_crypto_context_get_encryption_protocol(self())); } - Option signature_protocol() { + Option signature_protocol() const noexcept { return to_string_opt(g_mime_crypto_context_get_signature_protocol(self())); } - Option key_exchange_protocol() { + Option 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 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 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; - - - Option 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(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 write_to_stream(const MimeFormatOptions& f_opts, + MimeStream& stream) const; /** * Write the object to a string. * * @return */ - Option object_to_string() const noexcept; + Option 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; + private: GMimeObject* self() const { return reinterpret_cast(object()); @@ -832,7 +936,7 @@ public: * @return string or nullopt */ Option 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 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 references() const noexcept; - /** - * Callback for for_each(). See GMimeObjectForEachFunc. - * - */ - using ForEachFunc = std::function; - /** * Recursively apply func tol all parts of this message * @@ -914,7 +1011,7 @@ public: * @return string or nullopt */ Option 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 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 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 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 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 signed_content_part() const { + return part(GMIME_MULTIPART_SIGNED_CONTENT); + } + + Option signed_signature_part() const { + return part(GMIME_MULTIPART_SIGNED_SIGNATURE); + } + + Option encrypted_version_part() const { + return part(GMIME_MULTIPART_ENCRYPTED_VERSION); + } + + Option 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 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(object()); } @@ -1168,12 +1301,9 @@ public: EnableOnlineCertificateChecks = GMIME_DECRYPT_ENABLE_ONLINE_CERTIFICATE_CHECKS }; - struct Decrypted { - MimeObject decrypted_object; - MimeDecryptResult decrypte_result; - }; - - Result decrypt(DecryptFlags flags=DecryptFlags::None, + using Decrypted = std::pair; + Result 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> - verify(VerifyFlags vflags=VerifyFlags::None) const noexcept; + // Result> verify(VerifyFlags vflags=VerifyFlags::None) const noexcept; + + Result> verify(const MimeCryptoContext& ctx, + VerifyFlags vflags=VerifyFlags::None) const noexcept; private: GMimeMultipartSigned* self() const { diff --git a/lib/message/test-mu-message.cc b/lib/message/test-mu-message.cc index 53269a50..257b4edd 100644 --- a/lib/message/test-mu-message.cc +++ b/lib/message/test-mu-message.cc @@ -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 { - - 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 { + // 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; }