From c7d4b01957745a333912ab415570858064843000 Mon Sep 17 00:00:00 2001 From: Kevin McCarthy Date: Thu, 11 Jul 2019 18:45:45 -0700 Subject: [PATCH] Process autocrypt headers. Create/update peer database accounts and gpg keys based on the headers. --- autocrypt/autocrypt.c | 146 +++++++++++++++ autocrypt/autocrypt.h | 22 +++ autocrypt/autocrypt_db.c | 342 ++++++++++++++++++++++++++++++++++ autocrypt/autocrypt_gpgme.c | 36 ++++ autocrypt/autocrypt_private.h | 15 +- mutt.h | 2 +- parse.c | 20 +- protos.h | 3 + 8 files changed, 579 insertions(+), 7 deletions(-) diff --git a/autocrypt/autocrypt.c b/autocrypt/autocrypt.c index c9269bcf..5207af4b 100644 --- a/autocrypt/autocrypt.c +++ b/autocrypt/autocrypt.c @@ -22,9 +22,13 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mime.h" #include "autocrypt.h" #include "autocrypt_private.h" +#ifdef HAVE_SYS_TIME_H +#include +#endif #include static int autocrypt_dir_init (int can_create) @@ -156,3 +160,145 @@ cleanup: mutt_buffer_pool_release (&keydata); return rv; } + +int mutt_autocrypt_process_autocrypt_header (HEADER *hdr, ENVELOPE *env) +{ + AUTOCRYPTHDR *ac_hdr, *valid_ac_hdr = NULL; + struct timeval now; + AUTOCRYPT_PEER *peer = NULL; + AUTOCRYPT_PEER_HISTORY *peerhist = NULL; + BUFFER *keyid = NULL; + int update_db = 0, insert_db = 0, insert_db_history = 0, import_gpg = 0; + int rv = -1; + + if (!option (OPTAUTOCRYPT)) + return 0; + + if (mutt_autocrypt_init (0)) + return -1; + + if (!hdr || !hdr->content || !env) + return 0; + + /* 1.1 spec says to skip emails with more than one From header */ + if (!env->from || env->from->next) + return 0; + + /* 1.1 spec also says to skip multipart/report emails */ + if (hdr->content->type == TYPEMULTIPART && + !(ascii_strcasecmp (hdr->content->subtype, "report"))) + return 0; + + /* Ignore emails that appear to be more than a week in the future, + * since they can block all future updates during that time. */ + gettimeofday (&now, NULL); + if (hdr->date_sent > (now.tv_sec + 7 * 24 * 60 * 60)) + return 0; + + for (ac_hdr = env->autocrypt; ac_hdr; ac_hdr = ac_hdr->next) + { + if (ac_hdr->invalid) + continue; + + /* NOTE: this assumes the processing is occurring right after + * mutt_parse_rfc822_line() and the from ADDR is still in the same + * form (intl) as the autocrypt header addr field */ + if (ascii_strcasecmp (env->from->mailbox, ac_hdr->addr)) + continue; + + /* 1.1 spec says ignore all, if more than one valid header is found. */ + if (valid_ac_hdr) + { + valid_ac_hdr = NULL; + break; + } + valid_ac_hdr = ac_hdr; + } + + if (mutt_autocrypt_db_peer_get (env->from, &peer) < 0) + goto cleanup; + + if (peer) + { + if (hdr->date_sent <= peer->autocrypt_timestamp) + { + rv = 0; + goto cleanup; + } + + if (hdr->date_sent > peer->last_seen) + { + update_db = 1; + peer->last_seen = hdr->date_sent; + } + + if (valid_ac_hdr) + { + update_db = 1; + peer->autocrypt_timestamp = hdr->date_sent; + peer->prefer_encrypt = valid_ac_hdr->prefer_encrypt; + if (mutt_strcmp (peer->keydata, valid_ac_hdr->keydata)) + { + import_gpg = 1; + insert_db_history = 1; + mutt_str_replace (&peer->keydata, valid_ac_hdr->keydata); + } + } + } + else if (valid_ac_hdr) + { + import_gpg = 1; + insert_db = 1; + insert_db_history = 1; + } + + if (!(import_gpg || insert_db || update_db)) + { + rv = 0; + goto cleanup; + } + + if (!peer) + { + peer = mutt_autocrypt_db_peer_new (); + peer->last_seen = hdr->date_sent; + peer->autocrypt_timestamp = hdr->date_sent; + peer->keydata = safe_strdup (valid_ac_hdr->keydata); + peer->prefer_encrypt = valid_ac_hdr->prefer_encrypt; + } + + if (import_gpg) + { + keyid = mutt_buffer_pool_get (); + if (mutt_autocrypt_gpgme_import_key (peer->keydata, keyid)) + goto cleanup; + mutt_str_replace (&peer->keyid, mutt_b2s (keyid)); + } + + if (insert_db && + mutt_autocrypt_db_peer_insert (env->from, peer)) + goto cleanup; + + if (update_db && + mutt_autocrypt_db_peer_update (env->from, peer)) + goto cleanup; + + if (insert_db_history) + { + peerhist = mutt_autocrypt_db_peer_history_new (); + peerhist->email_msgid = safe_strdup (env->message_id); + peerhist->timestamp = hdr->date_sent; + peerhist->keydata = safe_strdup (peer->keydata); + if (mutt_autocrypt_db_peer_history_insert (env->from, peerhist)) + goto cleanup; + } + + rv = 0; + +cleanup: + mutt_autocrypt_db_peer_free (&peer); + mutt_autocrypt_db_peer_history_free (&peerhist); + mutt_buffer_pool_release (&keyid); + + return rv; +} diff --git a/autocrypt/autocrypt.h b/autocrypt/autocrypt.h index c690fae2..859a708a 100644 --- a/autocrypt/autocrypt.h +++ b/autocrypt/autocrypt.h @@ -32,7 +32,29 @@ typedef struct int enabled; } AUTOCRYPT_ACCOUNT; +typedef struct +{ + char *email_addr; + sqlite3_int64 last_seen; + sqlite3_int64 autocrypt_timestamp; + char *keyid; + char *keydata; + int prefer_encrypt; /* 0 = nopref, 1 = mutual */ + sqlite3_int64 gossip_timestamp; + char *gossip_keyid; + char *gossip_keydata; +} AUTOCRYPT_PEER; + +typedef struct +{ + char *peer_email_addr; + char *email_msgid; + sqlite3_int64 timestamp; + char *keydata; +} AUTOCRYPT_PEER_HISTORY; + int mutt_autocrypt_init (int); void mutt_autocrypt_cleanup (void); +int mutt_autocrypt_process_autocrypt_header (HEADER *hdr, ENVELOPE *env); #endif diff --git a/autocrypt/autocrypt_db.c b/autocrypt/autocrypt_db.c index a053c433..604c1f34 100644 --- a/autocrypt/autocrypt_db.c +++ b/autocrypt/autocrypt_db.c @@ -25,6 +25,14 @@ #include "autocrypt.h" #include "autocrypt_private.h" +/* Prepared statements */ +static sqlite3_stmt *AccountGetStmt; +static sqlite3_stmt *AccountInsertStmt; +static sqlite3_stmt *PeerGetStmt; +static sqlite3_stmt *PeerInsertStmt; +static sqlite3_stmt *PeerUpdateStmt; +static sqlite3_stmt *PeerHistoryInsertStmt; + static int autocrypt_db_create (const char *db_path) { if (sqlite3_open_v2 (db_path, @@ -96,6 +104,16 @@ void mutt_autocrypt_db_close (void) sqlite3_finalize (AccountInsertStmt); AccountInsertStmt = NULL; + sqlite3_finalize (PeerGetStmt); + PeerGetStmt = NULL; + sqlite3_finalize (PeerInsertStmt); + PeerInsertStmt = NULL; + sqlite3_finalize (PeerUpdateStmt); + PeerUpdateStmt = NULL; + + sqlite3_finalize (PeerHistoryInsertStmt); + PeerHistoryInsertStmt = NULL; + sqlite3_close_v2 (AutocryptDB); AutocryptDB = NULL; } @@ -263,3 +281,327 @@ cleanup: sqlite3_reset (AccountInsertStmt); return rv; } + +AUTOCRYPT_PEER *mutt_autocrypt_db_peer_new (void) +{ + return safe_calloc (1, sizeof(AUTOCRYPT_PEER)); +} + +void mutt_autocrypt_db_peer_free (AUTOCRYPT_PEER **peer) +{ + if (!peer || !*peer) + return; + FREE (&(*peer)->email_addr); + FREE (&(*peer)->keyid); + FREE (&(*peer)->keydata); + FREE (&(*peer)->gossip_keyid); + FREE (&(*peer)->gossip_keydata); + FREE (peer); /* __FREE_CHECKED__ */ +} + +int mutt_autocrypt_db_peer_get (ADDRESS *addr, AUTOCRYPT_PEER **peer) +{ + int rv = -1, result; + char *email = NULL; + + email = normalize_email_addr (addr); + *peer = NULL; + + if (!PeerGetStmt) + { + if (sqlite3_prepare_v2 ( + AutocryptDB, + "SELECT " + "email_addr, " + "last_seen, " + "autocrypt_timestamp, " + "keyid, " + "keydata, " + "prefer_encrypt, " + "gossip_timestamp, " + "gossip_keyid, " + "gossip_keydata " + "FROM peer " + "WHERE email_addr = ?", + -1, + &PeerGetStmt, + NULL) != SQLITE_OK) + goto cleanup; + } + + if (sqlite3_bind_text (PeerGetStmt, + 1, + email, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + + result = sqlite3_step (PeerGetStmt); + if (result != SQLITE_ROW) + { + if (result == SQLITE_DONE) + rv = 0; + goto cleanup; + } + + *peer = mutt_autocrypt_db_peer_new (); + (*peer)->email_addr = strdup_column_text (PeerGetStmt, 0); + (*peer)->last_seen = sqlite3_column_int64 (PeerGetStmt, 1); + (*peer)->autocrypt_timestamp = sqlite3_column_int64 (PeerGetStmt, 2); + (*peer)->keyid = strdup_column_text (PeerGetStmt, 3); + (*peer)->keydata = strdup_column_text (PeerGetStmt, 4); + (*peer)->prefer_encrypt = sqlite3_column_int (PeerGetStmt, 5); + (*peer)->gossip_timestamp = sqlite3_column_int64 (PeerGetStmt, 6); + (*peer)->gossip_keyid = strdup_column_text (PeerGetStmt, 7); + (*peer)->gossip_keydata = strdup_column_text (PeerGetStmt, 8); + + rv = 1; + +cleanup: + FREE (&email); + sqlite3_reset (PeerGetStmt); + return rv; +} + +int mutt_autocrypt_db_peer_insert (ADDRESS *addr, AUTOCRYPT_PEER *peer) +{ + int rv = -1; + char *email = NULL; + + email = normalize_email_addr (addr); + + if (!PeerInsertStmt) + { + if (sqlite3_prepare_v2 ( + AutocryptDB, + "INSERT INTO peer " + "(email_addr, " + "last_seen, " + "autocrypt_timestamp, " + "keyid, " + "keydata, " + "prefer_encrypt, " + "gossip_timestamp, " + "gossip_keyid, " + "gossip_keydata) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);", + -1, + &PeerInsertStmt, + NULL) != SQLITE_OK) + goto cleanup; + } + + if (sqlite3_bind_text (PeerInsertStmt, + 1, + email, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerInsertStmt, + 2, + peer->last_seen) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerInsertStmt, + 3, + peer->autocrypt_timestamp) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerInsertStmt, + 4, + peer->keyid, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerInsertStmt, + 5, + peer->keydata, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int (PeerInsertStmt, + 6, + peer->prefer_encrypt) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerInsertStmt, + 7, + peer->gossip_timestamp) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerInsertStmt, + 8, + peer->gossip_keyid, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerInsertStmt, + 9, + peer->gossip_keydata, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + + if (sqlite3_step (PeerInsertStmt) != SQLITE_DONE) + goto cleanup; + + rv = 0; + +cleanup: + FREE (&email); + sqlite3_reset (PeerInsertStmt); + return rv; +} + +int mutt_autocrypt_db_peer_update (ADDRESS *addr, AUTOCRYPT_PEER *peer) +{ + int rv = -1; + char *email = NULL; + + email = normalize_email_addr (addr); + + if (!PeerUpdateStmt) + { + if (sqlite3_prepare_v2 ( + AutocryptDB, + "UPDATE peer SET " + "last_seen = ?, " + "autocrypt_timestamp = ?, " + "keyid = ?, " + "keydata = ?, " + "prefer_encrypt = ?, " + "gossip_timestamp = ?, " + "gossip_keyid = ?, " + "gossip_keydata = ? " + "WHERE email_addr = ?;", + -1, + &PeerUpdateStmt, + NULL) != SQLITE_OK) + goto cleanup; + } + + if (sqlite3_bind_int64 (PeerUpdateStmt, + 1, + peer->last_seen) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerUpdateStmt, + 2, + peer->autocrypt_timestamp) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerUpdateStmt, + 3, + peer->keyid, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerUpdateStmt, + 4, + peer->keydata, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int (PeerUpdateStmt, + 5, + peer->prefer_encrypt) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerUpdateStmt, + 6, + peer->gossip_timestamp) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerUpdateStmt, + 7, + peer->gossip_keyid, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerUpdateStmt, + 8, + peer->gossip_keydata, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerUpdateStmt, + 9, + email, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + + if (sqlite3_step (PeerUpdateStmt) != SQLITE_DONE) + goto cleanup; + + rv = 0; + +cleanup: + FREE (&email); + sqlite3_reset (PeerUpdateStmt); + return rv; +} + +AUTOCRYPT_PEER_HISTORY *mutt_autocrypt_db_peer_history_new (void) +{ + return safe_calloc (1, sizeof(AUTOCRYPT_PEER_HISTORY)); +} + +void mutt_autocrypt_db_peer_history_free (AUTOCRYPT_PEER_HISTORY **peerhist) +{ + if (!peerhist || !*peerhist) + return; + FREE (&(*peerhist)->peer_email_addr); + FREE (&(*peerhist)->email_msgid); + FREE (&(*peerhist)->keydata); + FREE (peerhist); /* __FREE_CHECKED__ */ +} + +int mutt_autocrypt_db_peer_history_insert (ADDRESS *addr, AUTOCRYPT_PEER_HISTORY *peerhist) +{ + int rv = -1; + char *email = NULL; + + email = normalize_email_addr (addr); + + if (!PeerHistoryInsertStmt) + { + if (sqlite3_prepare_v2 ( + AutocryptDB, + "INSERT INTO peer_history " + "(peer_email_addr, " + "email_msgid, " + "timestamp, " + "keydata) " + "VALUES (?, ?, ?, ?);", + -1, + &PeerHistoryInsertStmt, + NULL) != SQLITE_OK) + goto cleanup; + } + + if (sqlite3_bind_text (PeerHistoryInsertStmt, + 1, + email, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerHistoryInsertStmt, + 2, + peerhist->email_msgid, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_int64 (PeerHistoryInsertStmt, + 3, + peerhist->timestamp) != SQLITE_OK) + goto cleanup; + if (sqlite3_bind_text (PeerHistoryInsertStmt, + 4, + peerhist->keydata, + -1, + SQLITE_STATIC) != SQLITE_OK) + goto cleanup; + + if (sqlite3_step (PeerHistoryInsertStmt) != SQLITE_DONE) + goto cleanup; + + rv = 0; + +cleanup: + FREE (&email); + sqlite3_reset (PeerHistoryInsertStmt); + return rv; +} diff --git a/autocrypt/autocrypt_gpgme.c b/autocrypt/autocrypt_gpgme.c index f37fca7f..952f648b 100644 --- a/autocrypt/autocrypt_gpgme.c +++ b/autocrypt/autocrypt_gpgme.c @@ -176,3 +176,39 @@ cleanup: gpgme_release (ctx); return rv; } + +int mutt_autocrypt_gpgme_import_key (const char *keydata, BUFFER *keyid) +{ + int rv = -1; + gpgme_ctx_t ctx = NULL; + BUFFER *raw_keydata = NULL; + gpgme_data_t dh = NULL; + gpgme_import_result_t result; + + if (create_gpgme_context (&ctx)) + goto cleanup; + + raw_keydata = mutt_buffer_pool_get (); + if (!mutt_buffer_from_base64 (raw_keydata, keydata)) + goto cleanup; + + if (gpgme_data_new_from_mem (&dh, mutt_b2s (raw_keydata), + mutt_buffer_len (raw_keydata), 0)) + goto cleanup; + + if (gpgme_op_import (ctx, dh)) + goto cleanup; + + result = gpgme_op_import_result (ctx); + if (!result->imports || !result->imports->fpr) + goto cleanup; + mutt_buffer_strcpy (keyid, result->imports->fpr); + + rv = 0; + +cleanup: + gpgme_data_release (dh); + gpgme_release (ctx); + mutt_buffer_pool_release (&raw_keydata); + return rv; +} diff --git a/autocrypt/autocrypt_private.h b/autocrypt/autocrypt_private.h index 8bb757a3..d43c9e83 100644 --- a/autocrypt/autocrypt_private.h +++ b/autocrypt/autocrypt_private.h @@ -32,14 +32,21 @@ int mutt_autocrypt_db_account_get (ADDRESS *addr, AUTOCRYPT_ACCOUNT **account); int mutt_autocrypt_db_account_insert (ADDRESS *addr, const char *keyid, const char *keydata, int prefer_encrypt); +AUTOCRYPT_PEER *mutt_autocrypt_db_peer_new (void); +void mutt_autocrypt_db_peer_free (AUTOCRYPT_PEER **peer); +int mutt_autocrypt_db_peer_get (ADDRESS *addr, AUTOCRYPT_PEER **peer); +int mutt_autocrypt_db_peer_insert (ADDRESS *addr, AUTOCRYPT_PEER *peer); +int mutt_autocrypt_db_peer_update (ADDRESS *addr, AUTOCRYPT_PEER *peer); + +AUTOCRYPT_PEER_HISTORY *mutt_autocrypt_db_peer_history_new (void); +void mutt_autocrypt_db_peer_history_free (AUTOCRYPT_PEER_HISTORY **peerhist); +int mutt_autocrypt_db_peer_history_insert (ADDRESS *addr, AUTOCRYPT_PEER_HISTORY *peerhist); + int mutt_autocrypt_schema_init (void); int mutt_autocrypt_schema_update (void); int mutt_autocrypt_gpgme_init (void); int mutt_autocrypt_gpgme_create_key (ADDRESS *addr, BUFFER *keyid, BUFFER *keydata); - -/* Prepared statements */ -sqlite3_stmt *AccountGetStmt; -sqlite3_stmt *AccountInsertStmt; +int mutt_autocrypt_gpgme_import_key (const char *keydata, BUFFER *keyid); #endif diff --git a/mutt.h b/mutt.h index 7e6cdc5a..d15ab106 100644 --- a/mutt.h +++ b/mutt.h @@ -680,7 +680,7 @@ typedef struct autocrypt char *keydata; unsigned int prefer_encrypt : 1; unsigned int invalid : 1; - struct autocrypt *next; /* used by gossip headers */ + struct autocrypt *next; } AUTOCRYPTHDR; #endif diff --git a/parse.c b/parse.c index 50b23b37..8dd9240a 100644 --- a/parse.c +++ b/parse.c @@ -29,6 +29,10 @@ #include "mutt_crypt.h" #include "url.h" +#ifdef USE_AUTOCRYPT +#include "autocrypt/autocrypt.h" +#endif + #include #include #include @@ -1131,8 +1135,11 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short #ifdef USE_AUTOCRYPT else if (ascii_strcasecmp (line+1, "utocrypt") == 0) { - e->autocrypt = parse_autocrypt (e->autocrypt, p); - matched = 1; + if (option (OPTAUTOCRYPT)) + { + e->autocrypt = parse_autocrypt (e->autocrypt, p); + matched = 1; + } } #endif break; @@ -1600,6 +1607,15 @@ ENVELOPE *mutt_read_rfc822_header (FILE *f, HEADER *hdr, short user_hdrs, dprint(1,(debugfile,"read_rfc822_header(): no date found, using received time from msg separator\n")); hdr->date_sent = hdr->received; } + +#ifdef USE_AUTOCRYPT + if (option (OPTAUTOCRYPT)) + { + mutt_autocrypt_process_autocrypt_header (hdr, e); + /* No sense in taking up memory after the header is processed */ + mutt_free_autocrypthdr (&e->autocrypt); + } +#endif } return (e); diff --git a/protos.h b/protos.h index 8a0616ba..8eec3b15 100644 --- a/protos.h +++ b/protos.h @@ -220,6 +220,9 @@ void mutt_format_s_tree (char *, size_t, const char *, const char *); void mutt_forward_intro (CONTEXT *ctx, HEADER *cur, FILE *fp); void mutt_forward_trailer (CONTEXT *ctx, HEADER *cur, FILE *fp); void mutt_free_alias (ALIAS **); +#ifdef USE_AUTOCRYPT +void mutt_free_autocrypthdr (AUTOCRYPTHDR **p); +#endif void mutt_free_body (BODY **); void mutt_free_color (int fg, int bg); void mutt_free_enter_state (ENTER_STATE **); -- 2.40.0