From 5a92c8b58aba0364e2cfde1cf48abdfdbbadf416 Mon Sep 17 00:00:00 2001 From: djcb Date: Sun, 22 Jul 2012 19:39:17 +0300 Subject: [PATCH] * crypto: decryption support (WIP) --- lib/mu-msg-crypto.c | 257 +++++++++++++++++++++++++++++++++++++------- lib/mu-msg-crypto.h | 81 +++++++++++++- lib/mu-msg-part.c | 27 ++--- lib/mu-msg-priv.h | 36 +++++++ lib/mu-msg-sexp.c | 2 +- lib/mu-util.h | 3 + 6 files changed, 352 insertions(+), 54 deletions(-) diff --git a/lib/mu-msg-crypto.c b/lib/mu-msg-crypto.c index 74fd8ede..55842236 100644 --- a/lib/mu-msg-crypto.c +++ b/lib/mu-msg-crypto.c @@ -21,6 +21,8 @@ #include "config.h" #endif /*HAVE_CONFIG_H*/ +#include + #include "mu-msg.h" #include "mu-msg-priv.h" #include "mu-msg-part.h" @@ -31,74 +33,138 @@ #include #include -static gboolean -dummy_password_requester (GMimeCryptoContext *ctx, const char *user_id, - const char* prompt_ctx, gboolean reprompt, - GMimeStream *response, GError **err) -{ - g_print ("password requested\n"); - return TRUE; +#define CALLBACK_DATA "callback-data" +struct _CallbackData { + MuMsgPartPasswordFunc pw_func; + gpointer user_data; +}; +typedef struct _CallbackData CallbackData; + + +static gboolean +password_requester (GMimeCryptoContext *ctx, const char *user_id, + const char* prompt_ctx, gboolean reprompt, + GMimeStream *response, GError **err) +{ + CallbackData *cbdata; + gchar *password; + ssize_t written; + + cbdata = g_object_get_data (G_OBJECT(ctx), CALLBACK_DATA); + g_return_val_if_fail (cbdata, FALSE); + + password = cbdata->pw_func (user_id, prompt_ctx, reprompt, + cbdata->user_data); + if (!password) + return FALSE; + + written = g_mime_stream_write_string (response, password); + if (written != -1) + written = g_mime_stream_write_string (response, "\n"); + if (written == -1) + mu_util_g_set_error (err, MU_ERROR_CRYPTO, + "writing password to mime stream failed"); + + if (g_mime_stream_flush (response) != 0) + g_printerr ("error flushing stream!\n"); + + memset (password, 0, strlen(password)); + g_free (password); + + return written != -1 ? TRUE : FALSE; } -GMimeCryptoContext* +static char* +dummy_password_func (const char *user_id, const char *prompt_ctx, + gboolean reprompt, gpointer user_data) +{ + g_print ("password requested for %s (%s) %s\n", + user_id, prompt_ctx, reprompt ? "again" : ""); + + return NULL; +} + + +static GMimeCryptoContext* get_gpg_crypto_context (MuMsgOptions opts, GError **err) { - GMimeCryptoContext *ctx; + GMimeCryptoContext *cctx; const char *prog; - ctx = NULL; + cctx = NULL; prog = g_getenv ("MU_GPG_PATH"); if (prog) - ctx = g_mime_gpg_context_new ( - (GMimePasswordRequestFunc)dummy_password_requester, - prog); + cctx = g_mime_gpg_context_new ( + (GMimePasswordRequestFunc)password_requester, prog); else { char *path; path = g_find_program_in_path ("gpg"); if (path) - ctx = g_mime_gpg_context_new ( - (GMimePasswordRequestFunc)dummy_password_requester, - path); + cctx = g_mime_gpg_context_new ( + password_requester, path); g_free (path); } - if (!ctx) { + if (!cctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get GPG crypto context"); return NULL; } g_mime_gpg_context_set_use_agent - (GMIME_GPG_CONTEXT(ctx), - opts & MU_MSG_OPTION_USE_AGENT); - g_mime_gpg_context_set_always_trust - (GMIME_GPG_CONTEXT(ctx), FALSE); + (GMIME_GPG_CONTEXT(cctx), + opts & MU_MSG_OPTION_USE_AGENT ? TRUE:FALSE); g_mime_gpg_context_set_auto_key_retrieve - (GMIME_GPG_CONTEXT(ctx), - opts & MU_MSG_OPTION_AUTO_RETRIEVE_KEY); + (GMIME_GPG_CONTEXT(cctx), + opts & MU_MSG_OPTION_AUTO_RETRIEVE_KEY ? TRUE:FALSE); + g_mime_gpg_context_set_always_trust + (GMIME_GPG_CONTEXT(cctx), FALSE); - return ctx; + return cctx; } -GMimeCryptoContext* +static GMimeCryptoContext* get_pkcs7_crypto_context (MuMsgOptions opts, GError **err) { - GMimeCryptoContext *ctx; + GMimeCryptoContext *cctx; - ctx = g_mime_pkcs7_context_new - ((GMimePasswordRequestFunc)dummy_password_requester); - if (!ctx) { + cctx = g_mime_pkcs7_context_new (password_requester); + if (!cctx) { mu_util_g_set_error (err, MU_ERROR, "failed to get PKCS7 crypto context"); return NULL; } g_mime_pkcs7_context_set_always_trust - (GMIME_PKCS7_CONTEXT(ctx), FALSE); + (GMIME_PKCS7_CONTEXT(cctx), FALSE); - return ctx; + return cctx; +} + + + +static GMimeCryptoContext* +get_crypto_context (MuMsgOptions opts, MuMsgPartPasswordFunc password_func, + gpointer user_data, GError **err) +{ + CallbackData *cbdata; + GMimeCryptoContext *cctx; + + if (opts & MU_MSG_OPTION_USE_PKCS7) + cctx = get_pkcs7_crypto_context (opts, err); + else + cctx = get_gpg_crypto_context (opts, err); + + /* use gobject to pass data to the callback func */ + cbdata = g_new0 (CallbackData, 1); + cbdata->pw_func = password_func; + cbdata->user_data = user_data; + + g_object_set_data_full (G_OBJECT(cctx), CALLBACK_DATA, + cbdata, (GDestroyNotify)g_free); + return cctx; } @@ -266,13 +332,11 @@ mu_msg_mime_sig_infos (GMimeMultipartSigned *sigmpart, MuMsgOptions opts, return NULL; /* error */ } - if (opts & MU_MSG_OPTION_USE_PKCS7) - cctx = get_pkcs7_crypto_context (opts, err); - else - cctx = get_gpg_crypto_context (opts, err); + /* dummy is good, since we don't need a password when checking + * signatures */ + cctx = get_crypto_context (opts, dummy_password_func, NULL, err); - /* return a fake siginfos with the error */ - if (!cctx) + if (!cctx) /* return a fake siginfos with the error */ return error_sig_infos (); /* error */ sigs = g_mime_multipart_signed_verify (sigmpart, cctx, err); @@ -313,7 +377,7 @@ mu_msg_part_free_sig_infos (GSList *siginfos) * - if not, the verdic is MU_MSG_PART_SIG_STATUS_UNKNOWN */ MuMsgPartSigStatus -mu_msg_mime_sig_infos_verdict (GSList *sig_infos) +mu_msg_part_sig_infos_verdict (GSList *sig_infos) { GSList *cur; MuMsgPartSigStatus status; @@ -457,3 +521,120 @@ mu_msg_part_sig_info_to_string (MuMsgPartSigInfo *info) return g_string_free (gstr, FALSE); } + + +struct _MuMsgDecryptedPart { + GMimeObject *decrypted; +}; + + +struct _PartData { + MuMsgPartDecryptForeachFunc func; + gpointer user_data; + GMimeCryptoContext *cctx; + GError *err; +}; +typedef struct _PartData PartData; + + +static void +each_encpart (GMimeObject *parent, GMimeObject *part, PartData *pdata) +{ + MuMsgDecryptedPart dpart; + + if (pdata->err) + return; + + if (!GMIME_IS_MULTIPART_ENCRYPTED (part)) + return; /* nothing to do for this part */ + + dpart.decrypted = + g_mime_multipart_encrypted_decrypt ( + GMIME_MULTIPART_ENCRYPTED(part), + pdata->cctx, NULL, &pdata->err); + if (!dpart.decrypted || pdata->err) { + if (!pdata->err) + mu_util_g_set_error + (&pdata->err, + MU_ERROR_CRYPTO, + "decryption failed"); + return; + } + + pdata->func (&dpart, pdata->user_data); +} + + +gboolean +mu_msg_part_decrypt_foreach (MuMsg *msg, MuMsgPartDecryptForeachFunc func, + MuMsgPartPasswordFunc password_func, + gpointer user_data, MuMsgOptions opts, GError **err) +{ + PartData pdata; + + g_return_val_if_fail (msg, FALSE); + g_return_val_if_fail (func, FALSE); + g_return_val_if_fail (password_func, FALSE); + + if (!mu_msg_load_msg_file (msg, err)) + return FALSE; + + pdata.func = func; + pdata.user_data = user_data; + pdata.err = NULL; + pdata.cctx = get_crypto_context (opts, password_func, + user_data, err); + if (!pdata.cctx) + return FALSE; + + g_mime_message_foreach (msg->_file->_mime_msg, + (GMimeObjectForeachFunc)each_encpart, + &pdata); + if (pdata.err) { + *err = pdata.err; + return FALSE; + } + + g_object_unref (pdata.cctx); + + return TRUE; +} + +char* +mu_msg_decrypted_part_to_string (MuMsgDecryptedPart *dpart, GError **err) +{ + GMimePart *part; + gboolean not_ok; + gchar *str; + + g_return_val_if_fail (dpart, NULL); + + if (!GMIME_IS_PART(dpart->decrypted)) { + mu_util_g_set_error (err, MU_ERROR_CRYPTO, + "wrong mime-type"); + return NULL; /* can only convert parts to string*/ + } + part = GMIME_PART(dpart->decrypted); + str = mu_msg_mime_part_to_string (part, ¬_ok); + + if (not_ok) { + g_free (str); + mu_util_g_set_error (err, MU_ERROR_CRYPTO, + "failed to convert part to string"); + return NULL; + } + + return str; +} + + +gboolean +mu_msg_decrypted_part_to_file (MuMsgDecryptedPart *dpart, const char *path, + GError **err) +{ + g_return_val_if_fail (dpart, FALSE); + g_return_val_if_fail (path, FALSE); + + return mu_msg_part_mime_save_object (dpart->decrypted, path, FALSE, FALSE, + err); +} diff --git a/lib/mu-msg-crypto.h b/lib/mu-msg-crypto.h index e9a6afc0..ac2eb4e3 100644 --- a/lib/mu-msg-crypto.h +++ b/lib/mu-msg-crypto.h @@ -80,7 +80,7 @@ const char* mu_msg_part_sig_status_to_string (MuMsgPartSigStatus status); /** - * summarize the signatures to one status: + * summarize the signature checks to one status: * * - if there's any signature with MU_MSG_PART_SIG_STATUS_(ERROR|FAIL), * the verdict is MU_MSG_PART_SIG_STATUS_ERROR @@ -94,7 +94,7 @@ const char* mu_msg_part_sig_status_to_string (MuMsgPartSigStatus status); * * @return the status */ -MuMsgPartSigStatus mu_msg_mime_sig_infos_verdict (GSList *sig_infos); +MuMsgPartSigStatus mu_msg_part_sig_infos_verdict (GSList *sig_infos); /** @@ -125,4 +125,81 @@ char* mu_msg_part_sig_info_to_string (MuMsgPartSigInfo *info) */ void mu_msg_part_free_sig_infos (GSList *siginfos); + + + + +/* + * below function only do anything useful if mu was built with crypto + * support + */ + +struct _MuMsgDecryptedPart; +typedef struct _MuMsgDecryptedPart MuMsgDecryptedPart; + + +/** + * callback function to provide decrypted message parts + * + * @param dpart a decrypted par + * @param user_data user pointer (as passed to mu_msg_part_decrypt_foreach) + */ +typedef void (*MuMsgPartDecryptForeachFunc) (MuMsgDecryptedPart *dpart, + gpointer user_data); + + +/** + * callback function to retrieve a password from the user + * + * @param user_id the user name / id to get the password for + * @param prompt_ctx a string containing some helpful context for the prompt + * @param reprompt whether this is a reprompt after an earlier, incorrect password + * @param user_data the user_data pointer passed to mu_msg_part_decrypt_foreach + * + * @return a newly allocated (g_free'able) string + */ +typedef char* (*MuMsgPartPasswordFunc) (const char *user_id, const char *prompt_ctx, + gboolean reprompt, gpointer user_data); + +/** + * go through all MIME-parts for this message, and decrypted all parts + * that are encrypted. After decryption, + * + * If mu was built without crypto support, function does nothing. + * + * @param msg a valid MuMsg instance + * @param fun a callback function called for each decrypted part + * @param password_func a callback func called to retrieve a password from user + * @param user_data user data which passed to the callback function + * @param opts options + * @param err receives error information + * + * @return TRUE if function succeeded, FALSE otherwise + */ +gboolean mu_msg_part_decrypt_foreach (MuMsg *msg, MuMsgPartDecryptForeachFunc func, + MuMsgPartPasswordFunc password_func, + gpointer user_data, MuMsgOptions opts, + GError **err); +/** + * convert the decrypted part to a string. + * + * @param dpart decrypted part + * @param err receives error information + * + * @return decrypted part as a string (g_free after use), or NULL + */ +char* mu_msg_decrypted_part_to_string (MuMsgDecryptedPart *dpart, GError **err); + +/** + * write the decrypted part to a file. + * + * @param dpart decrypted part + * @param path path to write it to + * @param err receives error information + * + * @return TRUE if it succeeded, FALSE otherwise + */ +gboolean mu_msg_decrypted_part_to_file (MuMsgDecryptedPart *dpart, const char *path, + GError **err); + #endif /*__MU_MSG_CRYPTO_H__*/ diff --git a/lib/mu-msg-part.c b/lib/mu-msg-part.c index 5958820d..c2a90645 100644 --- a/lib/mu-msg-part.c +++ b/lib/mu-msg-part.c @@ -242,13 +242,11 @@ msg_part_free (MuMsgPart *pi) #endif /*BUILD_CRYPTO*/ } - +#ifdef BUILD_CRYPTO static void check_signature_maybe (GMimeObject *parent, GMimeObject *mobj, MuMsgPart *pi, MuMsgOptions opts) { -#ifdef BUILD_CRYPTO - GMimeContentType *ctype; GError *err; gboolean pkcs7; @@ -275,10 +273,10 @@ check_signature_maybe (GMimeObject *parent, GMimeObject *mobj, MuMsgPart *pi, g_warning ("error verifying signature: %s", err->message); g_clear_error (&err); } +} + #endif /*BUILD_CRYPTO*/ - return; -} static void @@ -307,7 +305,9 @@ part_foreach_cb (GMimeObject *parent, GMimeObject *mobj, PartData *pdata) return; rv = init_msg_part_from_mime_message_part (mmsg, &pi); if (rv && (pdata->_opts && MU_MSG_OPTION_RECURSE_RFC822)) - /* NOTE: this screws up the counting (pdata->_idx) */ + /* NOTE: this screws up the counting + * (pdata->_idx); happily, we only use it + * where we don't care about that */ g_mime_message_foreach /* recurse */ (mmsg, (GMimeObjectForeachFunc)part_foreach_cb, pdata); @@ -315,8 +315,10 @@ part_foreach_cb (GMimeObject *parent, GMimeObject *mobj, PartData *pdata) rv = FALSE; /* ignore */ /* if we have crypto support, check the signature if there is one */ +#ifdef BUILD_CRYPTO if (pdata->_opts & MU_MSG_OPTION_CHECK_SIGNATURES) check_signature_maybe (parent, mobj, &pi, pdata->_opts); +#endif /*BUILD_CRYPTO*/ if (rv) pdata->_func(pdata->_msg, &pi, pdata->_user_data); @@ -352,7 +354,7 @@ mu_msg_part_foreach (MuMsg *msg, MuMsgPartForeachFunc func, gpointer user_data, } -static gboolean +gboolean write_part_to_fd (GMimePart *part, int fd, GError **err) { GMimeStream *stream; @@ -416,9 +418,9 @@ write_object_to_fd (GMimeObject *obj, int fd, GError **err) -static gboolean -save_mime_object (GMimeObject *obj, const char *fullpath, - gboolean overwrite, gboolean use_existing, GError **err) +gboolean +mu_msg_part_mime_save_object (GMimeObject *obj, const char *fullpath, + gboolean overwrite, gboolean use_existing, GError **err) { int fd; gboolean rv; @@ -498,7 +500,6 @@ mu_msg_part_filepath (MuMsg *msg, const char* targetdir, guint partidx, - gchar* mu_msg_part_filepath_cache (MuMsg *msg, guint partid) { @@ -556,8 +557,8 @@ mu_msg_part_save (MuMsg *msg, const char *fullpath, guint partidx, partidx); return FALSE; } else - return save_mime_object (part, fullpath, overwrite, - use_cached, err); + return mu_msg_part_mime_save_object (part, fullpath, overwrite, + use_cached, err); } diff --git a/lib/mu-msg-priv.h b/lib/mu-msg-priv.h index fc7897fa..68665a6d 100644 --- a/lib/mu-msg-priv.h +++ b/lib/mu-msg-priv.h @@ -76,6 +76,23 @@ struct _MuMsg { gchar* mu_msg_mime_part_to_string (GMimePart *part, gboolean *err); +/** + * write a GMimeObject to a file + * + * @param obj a GMimeObject + * @param fullpath full file path + * @param overwrite allow overwriting existing file + * @param if file already exist, don't bother to write + * @param err receives error information + * + * @return TRUE if writing succeeded, FALSE otherwise. + */ +gboolean mu_msg_part_mime_save_object (GMimeObject *obj, const char *fullpath, + gboolean overwrite, gboolean use_existing, + GError **err); + + + /** * get the MIME part that's probably the body of the message (heuristic) * @@ -87,6 +104,9 @@ gchar* mu_msg_mime_part_to_string (GMimePart *part, gboolean *err); GMimePart* mu_msg_mime_get_body_part (GMimeMessage *msg, gboolean want_html); + + + #ifdef BUILD_CRYPTO /** * get signature information for the mime part @@ -100,6 +120,22 @@ GMimePart* mu_msg_mime_get_body_part (GMimeMessage *msg, gboolean want_html); */ GSList* mu_msg_mime_sig_infos (GMimeMultipartSigned *sigmpart, MuMsgOptions opts, GError **err); + + + +/** + * decrypt the given mime part + * + * @param encpart + * @param opts + * @param err + * + * @return + */ +char* mu_msg_mime_decrypt (GMimeMultipartEncrypted *encpart, + MuMsgOptions opts, GError **err); + + #endif /*BUILD_CRYPTO*/ G_END_DECLS diff --git a/lib/mu-msg-sexp.c b/lib/mu-msg-sexp.c index 50f22556..855e3b97 100644 --- a/lib/mu-msg-sexp.c +++ b/lib/mu-msg-sexp.c @@ -242,7 +242,7 @@ elvis (const char *s1, const char *s2) static const char* sig_verdict (GSList *sig_infos) { - switch (mu_msg_mime_sig_infos_verdict (sig_infos)) { + switch (mu_msg_part_sig_infos_verdict (sig_infos)) { case MU_MSG_PART_SIG_STATUS_GOOD: return ":signature good"; case MU_MSG_PART_SIG_STATUS_BAD: diff --git a/lib/mu-util.h b/lib/mu-util.h index f4441810..da4553dd 100644 --- a/lib/mu-util.h +++ b/lib/mu-util.h @@ -414,6 +414,9 @@ enum _MuError { MU_ERROR_CONTACTS = 50, MU_ERROR_CONTACTS_CANNOT_RETRIEVE = 51, + /* crypto related errors */ + MU_ERROR_CRYPTO = 60, + /* File errors */ /* generic file-related error */