]> granicus.if.org Git - neomutt/commitdiff
Process autocrypt headers
authorKevin McCarthy <kevin@8t8.us>
Fri, 12 Jul 2019 01:45:45 +0000 (18:45 -0700)
committerRichard Russon <rich@flatcap.org>
Mon, 19 Aug 2019 23:14:27 +0000 (00:14 +0100)
Create/update peer database accounts and gpg keys based on the headers.

Co-authored-by: Richard Russon <rich@flatcap.org>
autocrypt/autocrypt.c
autocrypt/autocrypt.h
autocrypt/autocrypt_db.c
autocrypt/autocrypt_gpgme.c
autocrypt/autocrypt_private.h
email/envelope.h
email/parse.c

index 7591b78470d91e8d6d154fcceb155e19a7bcba4f..370a8638710cc4e700a9dc3a07537964be18d7a8 100644 (file)
 #include <errno.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
 #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;
+}
index 33f7b913973f2451fa7375b3673e0c52c0edb6a9..72f35a804833a7d77de5a002b032159df8049938 100644 (file)
@@ -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 */
index c0aafa84a69eca9d0d32434d595cfa899dd04136..0bddfe40a13704f4412ee09db61cc3349c25bf13 100644 (file)
 #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;
+}
index 0d2ac35982b34ee2add06e94371e2c610cbdaae5..a4259a8cabf2ff2ff51ed86bea90a168dbc598a3 100644 (file)
@@ -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;
+}
index bced9947c04d3fac8a35ed2cb897384878e59429..e63fbf352c5ab3bb7bbdffea592de0374329efb9 100644 (file)
@@ -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 */
index bb35e8c76b108e793f025514da0ac50d1cfae6a9..7cf51dcdb115571c3d74b3b761a37bcd22e9be46 100644 (file)
@@ -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 */
index 0a682cb272b50a10c78fa1cef4b9ffa2cf364bc9..e1a4969a08add7ec3f65249a7e6ec44731e74457 100644 (file)
 #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;