From: Kevin McCarthy Date: Fri, 12 Jul 2019 01:45:45 +0000 (-0700) Subject: Process autocrypt headers X-Git-Tag: 2019-10-25~97^2~45 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6c38bd672b6b6046108c6fc2f406f52e3dc936e2;p=neomutt Process autocrypt headers Create/update peer database accounts and gpg keys based on the headers. Co-authored-by: Richard Russon --- diff --git a/autocrypt/autocrypt.c b/autocrypt/autocrypt.c index 7591b7847..370a86387 100644 --- a/autocrypt/autocrypt.c +++ b/autocrypt/autocrypt.c @@ -24,10 +24,12 @@ #include #include #include +#include #include #include #include "autocrypt_private.h" #include "address/lib.h" +#include "email/lib.h" #include "mutt.h" #include "autocrypt.h" #include "curs_lib.h" @@ -166,3 +168,144 @@ cleanup: mutt_buffer_pool_release(&keydata); return rc; } + +int mutt_autocrypt_process_autocrypt_header(struct Email *e, struct Envelope *env) +{ + struct AutocryptHeader *ac_hdr, *valid_ac_hdr = NULL; + struct timeval now; + struct AutocryptPeer *peer = NULL; + struct AutocryptPeerHistory *peerhist = NULL; + struct Buffer *keyid = NULL; + int update_db = 0, insert_db = 0, insert_db_history = 0, import_gpg = 0; + int rv = -1; + + if (!C_Autocrypt) + return 0; + + if (mutt_autocrypt_init(0)) + return -1; + + if (!e || !e->content || !env) + return 0; + + /* 1.1 spec says to skip emails with more than one From header */ + struct Address *from = TAILQ_FIRST(&env->from); + if (!from || TAILQ_NEXT(from, entries)) + return 0; + + /* 1.1 spec also says to skip multipart/report emails */ + if ((e->content->type == TYPE_MULTIPART) && + !(mutt_str_strcasecmp(e->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 (e->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 (mutt_str_strcasecmp(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(from, &peer) < 0) + goto cleanup; + + if (peer) + { + if (e->date_sent <= peer->autocrypt_timestamp) + { + rv = 0; + goto cleanup; + } + + if (e->date_sent > peer->last_seen) + { + update_db = 1; + peer->last_seen = e->date_sent; + } + + if (valid_ac_hdr) + { + update_db = 1; + peer->autocrypt_timestamp = e->date_sent; + peer->prefer_encrypt = valid_ac_hdr->prefer_encrypt; + if (mutt_str_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 = e->date_sent; + peer->autocrypt_timestamp = e->date_sent; + peer->keydata = mutt_str_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(from, peer)) + goto cleanup; + + if (update_db && mutt_autocrypt_db_peer_update(from, peer)) + goto cleanup; + + if (insert_db_history) + { + peerhist = mutt_autocrypt_db_peer_history_new(); + peerhist->email_msgid = mutt_str_strdup(env->message_id); + peerhist->timestamp = e->date_sent; + peerhist->keydata = mutt_str_strdup(peer->keydata); + if (mutt_autocrypt_db_peer_history_insert(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 33f7b9139..72f35a804 100644 --- a/autocrypt/autocrypt.h +++ b/autocrypt/autocrypt.h @@ -37,7 +37,29 @@ struct AutocryptAccount int enabled; }; +struct AutocryptPeer +{ + 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; +}; + +struct AutocryptPeerHistory +{ + char *peer_email_addr; + char *email_msgid; + sqlite3_int64 timestamp; + char *keydata; +}; + int mutt_autocrypt_init (int); void mutt_autocrypt_cleanup (void); +int mutt_autocrypt_process_autocrypt_header (struct Email *hdr, struct Envelope *env); #endif /* MUTT_AUTOCRYPT_AUTOCRYPT_H */ diff --git a/autocrypt/autocrypt_db.c b/autocrypt/autocrypt_db.c index c0aafa84a..0bddfe40a 100644 --- a/autocrypt/autocrypt_db.c +++ b/autocrypt/autocrypt_db.c @@ -30,6 +30,14 @@ #include "autocrypt.h" #include "globals.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, &AutocryptDB, @@ -94,6 +102,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; } @@ -236,3 +254,243 @@ cleanup: sqlite3_reset(AccountInsertStmt); return rv; } + +struct AutocryptPeer *mutt_autocrypt_db_peer_new(void) +{ + return mutt_mem_calloc(1, sizeof(struct AutocryptPeer)); +} + +void mutt_autocrypt_db_peer_free(struct AutocryptPeer **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); +} + +int mutt_autocrypt_db_peer_get(struct Address *addr, struct AutocryptPeer **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(struct Address *addr, struct AutocryptPeer *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(struct Address *addr, struct AutocryptPeer *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; +} + +struct AutocryptPeerHistory *mutt_autocrypt_db_peer_history_new(void) +{ + return mutt_mem_calloc(1, sizeof(struct AutocryptPeerHistory)); +} + +void mutt_autocrypt_db_peer_history_free(struct AutocryptPeerHistory **peerhist) +{ + if (!peerhist || !*peerhist) + return; + FREE(&(*peerhist)->peer_email_addr); + FREE(&(*peerhist)->email_msgid); + FREE(&(*peerhist)->keydata); + FREE(peerhist); +} + +int mutt_autocrypt_db_peer_history_insert(struct Address *addr, + struct AutocryptPeerHistory *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 0d2ac3598..a4259a8ca 100644 --- a/autocrypt/autocrypt_gpgme.c +++ b/autocrypt/autocrypt_gpgme.c @@ -170,3 +170,38 @@ cleanup: gpgme_release(ctx); return rv; } + +int mutt_autocrypt_gpgme_import_key(const char *keydata, struct Buffer *keyid) +{ + int rv = -1; + gpgme_ctx_t ctx = NULL; + struct 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_b64_buffer_decode(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 bced9947c..e63fbf352 100644 --- a/autocrypt/autocrypt_private.h +++ b/autocrypt/autocrypt_private.h @@ -39,14 +39,21 @@ int mutt_autocrypt_db_account_get (struct Address *addr, struct AutocryptAccount int mutt_autocrypt_db_account_insert (struct Address *addr, const char *keyid, const char *keydata, int prefer_encrypt); +struct AutocryptPeer *mutt_autocrypt_db_peer_new (void); +void mutt_autocrypt_db_peer_free (struct AutocryptPeer **peer); +int mutt_autocrypt_db_peer_get (struct Address *addr, struct AutocryptPeer **peer); +int mutt_autocrypt_db_peer_insert (struct Address *addr, struct AutocryptPeer *peer); +int mutt_autocrypt_db_peer_update (struct Address *addr, struct AutocryptPeer *peer); + +struct AutocryptPeerHistory *mutt_autocrypt_db_peer_history_new (void); +void mutt_autocrypt_db_peer_history_free (struct AutocryptPeerHistory **peerhist); +int mutt_autocrypt_db_peer_history_insert (struct Address *addr, struct AutocryptPeerHistory *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 (struct Address *addr, struct Buffer *keyid, struct Buffer *keydata); - -/* Prepared statements */ -sqlite3_stmt *AccountGetStmt; -sqlite3_stmt *AccountInsertStmt; +int mutt_autocrypt_gpgme_import_key (const char *keydata, struct Buffer *keyid); #endif /* MUTT_AUTOCRYPT_AUTOCRYPT_PRIVATE_H */ diff --git a/email/envelope.h b/email/envelope.h index bb35e8c76..7cf51dcdb 100644 --- a/email/envelope.h +++ b/email/envelope.h @@ -41,7 +41,7 @@ struct AutocryptHeader char *keydata; unsigned int prefer_encrypt : 1; unsigned int invalid : 1; - struct AutocryptHeader *next; /* used by gossip headers */ + struct AutocryptHeader *next; }; #endif @@ -93,6 +93,7 @@ void mutt_env_to_local (struct Envelope *e); #ifdef USE_AUTOCRYPT #define mutt_new_autocrypthdr() mutt_mem_calloc(1, sizeof(struct AutocryptHeader)) +void mutt_free_autocrypthdr (struct AutocryptHeader **p); #endif #endif /* MUTT_EMAIL_ENVELOPE_H */ diff --git a/email/parse.c b/email/parse.c index 0a682cb27..e1a4969a0 100644 --- a/email/parse.c +++ b/email/parse.c @@ -41,11 +41,15 @@ #include "email_globals.h" #include "envelope.h" #include "from.h" +#include "globals.h" #include "mime.h" #include "parameter.h" #include "rfc2047.h" #include "rfc2231.h" #include "url.h" +#ifdef USE_AUTOCRYPT +#include "autocrypt/autocrypt.h" +#endif /* If the 'Content-Length' is bigger than 1GiB, then it's clearly wrong. * Cap the value to prevent overflow of Body.length */ @@ -943,8 +947,11 @@ int mutt_rfc822_parse_line(struct Envelope *env, struct Email *e, char *line, #ifdef USE_AUTOCRYPT else if (mutt_str_strcasecmp(line + 1, "utocrypt") == 0) { - env->autocrypt = parse_autocrypt(env->autocrypt, p); - matched = 1; + if (C_Autocrypt) + { + env->autocrypt = parse_autocrypt(env->autocrypt, p); + matched = 1; + } } #endif break; @@ -1243,6 +1250,15 @@ struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hd "no date found, using received time from msg separator\n"); e->date_sent = e->received; } + +#ifdef USE_AUTOCRYPT + if (C_Autocrypt) + { + mutt_autocrypt_process_autocrypt_header(e, env); + /* No sense in taking up memory after the header is processed */ + mutt_free_autocrypthdr(&env->autocrypt); + } +#endif } return env;