[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/autocrypt 25635d8441 01/94: initial import
From: |
ELPA Syncer |
Subject: |
[elpa] externals/autocrypt 25635d8441 01/94: initial import |
Date: |
Sun, 26 Mar 2023 07:57:56 -0400 (EDT) |
branch: externals/autocrypt
commit 25635d8441c38c009316bc65197593db39fb9359
Author: Philip K <philip@warpmail.net>
Commit: Philip K <philip@warpmail.net>
initial import
---
autocrypt.el | 595 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 595 insertions(+)
diff --git a/autocrypt.el b/autocrypt.el
new file mode 100644
index 0000000000..b8d52610a2
--- /dev/null
+++ b/autocrypt.el
@@ -0,0 +1,595 @@
+;;; autocrypt.el --- Autocrypt implementation -*- lexical-binding:t -*-
+
+;; Author: Philip K. <philip@warpmail.net>
+;; Version: 0.4.0
+;; Keywords: comm
+;; Package-Requires: ((emacs "24.1"))
+;; URL: https://git.sr.ht/~zge/autocrypt
+
+;; This file is NOT part of Emacs.
+;;
+;; This file is in the public domain, to the extent possible under law,
+;; published under the CC0 1.0 Universal license.
+;;
+;; For a full copy of the CC0 license see
+;; https://creativecommons.org/publicdomain/zero/1.0/legalcode
+
+;;; Commentary:
+
+;; Implementation of Autocrypt (https://autocrypt.org/) for various
+;; Emacs MUAs.
+
+;; Run M-x `autocrypt-create-account' to initialise an autocrypt key,
+;; and add `autocrypt-mode' to your MUA's hooks (`gnus-mode-hook',
+;; `message-mode-hook', ...) to activate it's usage.
+
+;;; Code:
+
+(require 'message)
+(require 'cl-lib)
+(require 'rx)
+
+ ;; CUSTOMIZABLES
+
+(defgroup autocrypt nil
+ "Autocrypt protocol implementation for Emacs MUAs"
+ :tag "Autocrypt"
+ :group 'mail
+ :link '(url-link "https://autocrypt.org/")
+ :version "28.1"
+ :prefix "autocrypt-")
+
+(defcustom autocrypt-accounts nil
+ "Alist of supported Autocrypt accounts.
+
+All elements have the form (MAIL FINGERPRINT PREFERENCE), where
+FINGERPRINT is the fingerprint of the PGP key that should be used
+by email address MAIL. PREFERENCE must be one of `mutual' or
+`no-preference', `none' (if no preference should be inserted into
+headers), or nil if this account should be temporarily disabled.
+
+This variable doesn't have to be manually specified, as
+activating the command `autocrypt-mode' should automatically
+configure it, or by calling `autocrypt-create-account'."
+ :type '(alist :key-type string
+ :value-type
+ (group (string :tag "Fingerprint")
+ (choice :tag "Encryption Preference"
+ (const :tag "None" none)
+ (const :tag "No Preference" no-preference)
+ (const :tag "Mutual" mutual)
+ (const :tag "Disable this Account" nil)))))
+
+(defcustom autocrypt-do-gossip t
+ "Enable Autocrypt gossiping.
+
+This will inject \"Autocrypt-Gossip\" headers when required, and
+process \"Autocrypt-Gossip\" headers when received."
+ :type '(choice (const :tag "Enable Gossip" t)
+ (const :tag "Only receive" 'only-receive)
+ (const :tag "Only send" 'only-send)
+ (const :tag "Disable Gossip" nil)))
+
+(defcustom autocrypt-save-file
+ (expand-file-name "autocrypt-data.el" user-emacs-directory)
+ "File where Autocrypt peer data should be saved."
+ :type '(file :must-match t))
+
+ ;; DATA STRUCTURES
+
+;; https://autocrypt.org/level1.html#communication-peers
+(cl-defstruct autocrypt-peer
+ last-seen
+ timestamp
+ pubkey
+ preference
+ gossip-timestamp
+ gossip-key
+ deactivated)
+
+ ;; INTERNAL STATE
+
+(defvar autocrypt-peers nil
+ "List of known autocrypt peers.
+
+Every member of this list has to be an instance of the
+`autocrypt-peer' structure.")
+
+(defconst autocrypt-save-variables '(autocrypt-peers)
+ "List of variables to save to `autocrypt-save-data'.")
+
+ ;; MUA TRANSLATION LAYER
+
+(defconst autocrypt-mua-func-alist
+ '((gnus
+ :install autocrypt-gnus-install
+ :uninstall autocrypt-gnus-uninstall
+ :header autocrypt-gnus-header)
+ (rmail
+ :install autocrypt-rmail-install
+ :uninstall autocrypt-rmail-uninstall
+ :header autocrypt-rmail-header)
+ (mu4e
+ :install autocrypt-mu4e-install
+ :uninstall autocrypt-mu4e-uninstall
+ :header autocrypt-mu4e-header)
+ (message
+ :install autocrypt-message-install
+ :uninstall autocrypt-message-uninstall))
+ "Alist for all MUA specific functions.
+
+The value of each record is a plist. The value of each property
+is the symbol of the function that should be called, when
+refereed to by the property (see `autocrypt-mua-call').
+
+Valid properties and their associated messages are:
+
+:install - called when `autocrypt-mode' is activated in the
+current buffer. Should only install hooks.
+
+:uninstall - inverse of :install, and should reverse it's
+effects, ie. usually removing hooks.
+
+:header - a MUA viewer (Gnus, Rmail, ...) must provide such a
+message to let autocrypt query headers. The function must accept
+one argument, and return the value as a string.
+
+:part - a MUA viewer might provide such a message to let
+autocrypt query parts. The function must accept a index (as
+in IDX'th part) and return either a buffer, string or
+cons-cell of the form (file . PATH), where PATH points to where
+the part contents can be found.")
+
+(defsubst autocrypt-get-mua ()
+ "Return key for MUA based on major mode.
+
+The key should identify a record in the
+`autocrypt-mua-func-alist' alist."
+ (cond
+ ((derived-mode-p 'gnus-mode)
+ 'gnus)
+ ((derived-mode-p 'rmail-mode)
+ 'rmail)
+ ((derived-mode-p 'message-mode)
+ 'message)
+ (t (user-error "MUA not supported"))))
+
+(defsubst autocrypt-mua-func (msg)
+ "Return function behind MSG for major mode."
+ (plist-get (cdr (assq (autocrypt-get-mua)
+ autocrypt-mua-func-alist))
+ msg))
+
+(defun autocrypt-mua-call (msg &rest args)
+ "Call function behind MSG with ARGS for major mode."
+ (let ((func (autocrypt-mua-func msg)))
+ (and func (apply func args))))
+
+ ;; INTERNAL FUNCTIONS
+
+;; https://autocrypt.org/level1.html#e-mail-address-canonicalization
+(defsubst autocrypt-canonicalise (addr)
+ "Return a canonical form of email address ADDR."
+ ;; "[...] Other canonicalization efforts are considered for later
+ ;; specification versions."
+ (save-match-data
+ (let ((parts (mail-extract-address-components addr)))
+ (downcase (cadr parts)))))
+
+(defun autocrypt-load-data ()
+ "Load peer data if exists from `autocrypt-save-file'."
+ (when (file-exists-p autocrypt-save-file)
+ (load autocrypt-save-file t t t)))
+
+(defun autocrypt-save-data ()
+ "Write peer data save-file to `autocrypt-save-file'."
+ (with-temp-buffer
+ (insert ";; generated by autocrypt.el.\n"
+ ";; do not modify by hand\n")
+ (let ((standard-output (current-buffer)))
+ (dolist (var autocrypt-save-variables)
+ (print
+ `(unless ,var
+ (setq ,var ',(symbol-value var))))))
+ (write-file autocrypt-save-file)))
+
+;;
https://autocrypt.org/level1.html#recommendations-for-single-recipient-messages
+(defun autocrypt-single-recommentation (sender recipient)
+ "Calculate autocrypt recommendation for a single RECIPIENT.
+
+SENDER is a string containing the value of the From field."
+ (let ((self (cdr (assoc (autocrypt-canonicalise sender) autocrypt-accounts)))
+ (peer (cdr (assoc (autocrypt-canonicalise recipient)
autocrypt-peers))))
+ ;; TODO: check if keys are revoked, expired or unusable
+ (cond
+ ((not (and peer (autocrypt-peer-pubkey peer)))
+ 'disabled)
+ ((and (eq (autocrypt-peer-preference peer) 'mutual)
+ (eq (cadr self) 'mutual))
+ 'encrypt)
+ ((or (eq (autocrypt-peer-preference peer) 'mutual)
+ (> (- (time-to-days (current-time))
+ (time-to-days (autocrypt-peer-last-seen peer)))
+ 35))
+ 'discourage)
+ (t 'available))))
+
+;;
https://autocrypt.org/level1.html#provide-a-recommendation-for-message-encryption
+(defun autocrypt-recommendation (sender recipients)
+ "Calculate a autocrypt recommendation for RECIPIENTS.
+
+SENDER is a string containing the value of the From field."
+ (let ((res (mapcar (apply-partially #'autocrypt-single-recommentation sender)
+ recipients)))
+ (cond
+ ((or (null res) (memq 'disabled res)) 'disabled)
+ ((memq 'discourage res) 'discourage)
+ ((null (delq 'encrypt res)) 'encrypt)
+ (t 'available))))
+
+;; https://autocrypt.org/level1.html#the-autocrypt-header
+(defun autocrypt-parse-header (string)
+ "Destruct Autocrypt header from STRING.
+
+Returns list with address, preference and key data if
+well-formed, otherwise returns just nil."
+ (with-temp-buffer
+ (save-excursion
+ (insert string))
+ (let (addr pref keydata)
+ (save-match-data
+ (while (looking-at (rx (group (+ (any alnum ?_ ?-)))
+ (* space) "=" (* space)
+ (group (* (not (any ";"))))
+ (? ";" (* space))))
+ (cond
+ ((string= (match-string 1) "addr")
+ (setq addr (autocrypt-canonicalise (match-string 2))))
+ ((string= (match-string 1) "prefer-encrypt")
+ (setq pref (cond
+ ((string= (match-string 2) "mutual")
+ 'mutual)
+ ((string= (match-string 2) "nopreference")
+ 'no-preference))))
+ ((string= (match-string 1) "keydata")
+ (setq keydata (base64-decode-string (match-string 2))))
+ ((string-prefix-p "_" (match-string 1)) ; ignore
+ nil)
+ (t (error "Unsupported Autocrypt key")))
+ (goto-char (match-end 0))))
+ (and addr keydata (list addr pref keydata)))))
+
+;;;
https://autocrypt.org/level1.html#updating-autocrypt-peer-state-from-key-gossip
+(defun autocrypt-process-gossip (date)
+ "Update internal autocrypt gossip state."
+ (let ((recip (autocrypt-list-recipients))
+ (root (autocrypt-mua-call :part 0))
+ (re (rx bol "Autocrypt-Gossip:" (* space)
+ (group (+ (or nonl (: "\n "))))
+ eol))
+ gossip)
+ (when root
+ (catch 'unsupported
+ (with-temp-buffer
+ ;; The MUA interface should have the flexibility to return
+ ;; different kinds of part containers. Currently
+ ;; buffers, strings and a cons-cell of the form '(file . PATH)
+ ;; are supported, where PATH is a string describing where to
+ ;; find the part.
+ (cond
+ ((bufferp root)
+ (insert (with-current-buffer root (buffer-string))))
+ ((stringp root)
+ (insert root))
+ ((eq (car root) 'file)
+ (insert-file-contents (cdr root)))
+ (t
+ ;; If unsupported, then the gossip processing should fail
+ ;; SILENTLY. Autocrypt should not annoy the user.
+ (throw 'unsupported nil)))
+ (ietf-drums-narrow-to-header)
+ (goto-char (point-min))
+ (save-match-data
+ (while (search-forward-regexp re nil t)
+ (push (autocrypt-parse-header (match-string 1))
+ gossip))))
+ (dolist (datum (delq nil gossip))
+ (let* ((addr (car datum))
+ (peer (cdr (assoc addr recip))))
+ (if (and peer (time-less-p (autocrypt-peer-gossip-timestamp peer)
+ date))
+ (setf (autocrypt-peer-gossip-timestamp date)
+ (autocrypt-peer-gossip-key (caddr datum)))
+ (push (cons addr (make-autocrypt-peer
+ :gossip-timestamp date
+ :gossip-key (caddr datum)))
+ autocrypt-peers))))))))
+
+;; https://autocrypt.org/level1.html#updating-autocrypt-peer-state
+(defun autocrypt-process-header ()
+ "Update internal autocrypt state."
+ (let* ((from (autocrypt-canonicalise (autocrypt-mua-call :header "From")))
+ (date (mail-header-parse-date (autocrypt-mua-call :header "Date")))
+ (header (autocrypt-mua-call :header "Autocrypt"))
+ parse addr preference keydata peer)
+ (when header
+ (when (setq parse (autocrypt-parse-header header))
+ (setq addr (autocrypt-canonicalise (car parse))
+ preference (cadr parse)
+ keydata (caddr parse)
+ peer (or (cdr (assoc addr autocrypt-peers))
+ (make-autocrypt-peer
+ :last-seen date
+ :timestamp date
+ :pubkey keydata
+ :preference preference)))))
+ (when autocrypt-do-gossip
+ (autocrypt-process-gossip date))
+ (when (string= from addr)
+ (unless (time-less-p date (autocrypt-peer-timestamp peer))
+ (when (time-less-p (autocrypt-peer-last-seen peer) date)
+ (setf (autocrypt-peer-last-seen peer) date))
+ (if keydata ; has "Autocrypt" header
+ (setf (autocrypt-peer-preference peer) (or preference 'none)
+ (autocrypt-peer-deactivated peer) nil
+ (autocrypt-peer-timestamp peer) date
+ (autocrypt-peer-pubkey peer) keydata)
+ (setf (autocrypt-peer-deactivated peer) t))
+ (unless (assoc addr autocrypt-peers)
+ (push (cons addr peer) autocrypt-peers))))))
+
+(defun autocrypt-insert-keydata (data)
+ "Insert raw keydata DATA as base64 at point."
+ (let ((start (point)))
+ (insert data)
+ (base64-encode-region start (point))
+ (save-restriction
+ (narrow-to-region start (point))
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (insert " ")
+ (forward-line))))))
+
+(defun autocrypt-get-keydata (acc)
+ "Generate key for ACC to insert into header."
+ (let* ((ctx (epg-make-context))
+ (keys (epg-list-keys ctx (cadr acc) 'secret)))
+ (epg-export-keys-to-string ctx keys)))
+
+(defun autocrypt-generate-header (addr &optional gossip-p)
+ "Generate header value for address ADDR.
+
+If ADDR is a local account, it's key will be used. Otherwise it
+attempts to look up ADDR in the peer data. If nothing was found
+OR the header is too large, return nil."
+ (let (acc peer pref keydata)
+ (cond
+ ((setq acc (assoc addr autocrypt-accounts))
+ (setq keydata (autocrypt-get-keydata acc)
+ pref (cadr acc)))
+ ((setq peer (assoc addr autocrypt-peers))
+ (setq keydata (autocrypt-peer-pubkey peer)
+ pref (autocrypt-peer-preference peer))))
+ (when (and (not (and peer (autocrypt-peer-deactivated peer)))
+ pref keydata)
+ (with-temp-buffer
+ (insert "addr=" addr "; ")
+ (cond
+ (gossip-p
+ ;; Key Gossip Injection [...] SHOULD NOT include a
+ ;; prefer-encrypt attribute.
+ nil)
+ ((eq pref 'no-preference)
+ (insert "prefer-encrypt=nopreference; "))
+ ((eq pref 'mutual)
+ (insert "prefer-encrypt=mutual; ")))
+ (insert "keydata=")
+ (autocrypt-insert-keydata keydata)
+ (and (< (buffer-size) (* 10 1024))
+ (buffer-string))))))
+
+(defun autocrypt-create-account (name email expire pass)
+ "Create a GPG key for Autocrypt."
+ (interactive (list (read-string "Name: " user-full-name)
+ (read-string "Email: " user-mail-address)
+ (read-string "Expire Date (date or <n>{d,w,m,y}): ")
+ (let ((prompt "Passphrase (must be non-empty): ") pass)
+ (while (eq (setq pass (read-passwd prompt t t)) t))
+ pass)))
+ (let ((ctx (epg-make-context)))
+ (epg-generate-key-from-string
+ ctx
+ (with-temp-buffer
+ ;;
https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html
+ ;;
https://lists.gnupg.org/pipermail/gnupg-users/2017-December/059622.html
+ (insert "Key-Type: eddsa\n")
+ (insert "Key-Curve: Ed25519\n")
+ (insert "Key-Usage: sign\n")
+ (insert "Subkey-Type: ecdh\n")
+ (insert "Subkey-Curve: Curve25519\n")
+ (insert "Subkey-Usage: encrypt\n")
+ (insert "Name-Real: " name "\n")
+ (insert "Name-Email: " email "\n")
+ (insert "Name-Comment: generated by autocrypt.el\n")
+ (insert "Passphrase: " pass "\n")
+ (insert "Expire-Date: " (if (string= "" expire) "0" expire) "\n")
+ (insert "%commit")
+ (buffer-string)))
+ (let ((res (epg-context-result-for ctx 'generate-key)))
+ (unless res
+ (error "Could not determine fingerprint"))
+ (customize-set-variable
+ 'autocrypt-accounts
+ (cons (list email (cdr (assq 'fingerprint (car res))) 'none)
+ autocrypt-accounts)
+ "Customized by autocrypt.el"))
+ (message "Successfully generated key for %s, and added to key chain."
+ email)))
+
+ ;; GNUS SUPPORT
+
+(with-eval-after-load 'gnus
+ ;; setup with (add-hook 'gnus-load-hook #'autocrypt-mode)
+
+ (defun autocrypt-gnus-install ()
+ "Install autocrypt hooks for Gnus."
+ (add-hook 'gnus-view-mode-hook #'autocrypt-process-header))
+
+ (defun autocrypt-gnus-uninstall ()
+ "Remove autocrypt hooks for Gnus."
+ (remove-hook 'gnus-view-mode-hook #'autocrypt-process-header))
+
+ (defun autocrypt-gnus-header (field)
+ "Ask Gnus to return header FIELD."
+ (gnus-fetch-original-field field)))
+
+ ;; RMAIL SUPPORT
+
+(with-eval-after-load 'rmail
+ ;; setup with (add-hook 'rmail-mode-hook #'autocrypt-mode)
+
+ (defun autocrypt-rmail-install ()
+ "Install autocrypt hooks for Rmail."
+ (add-hook 'rmail-show-message-hook #'autocrypt-process-header))
+
+ (defun autocrypt-rmail-uninstall ()
+ "Remove autocrypt hooks for Rmail."
+ (remove-hook 'rmail-show-message-hook #'autocrypt-process-header))
+
+ (defun autocrypt-rmail-header (field)
+ "Ask Rmail to return header field."
+ (rmail-apply-in-message
+ rmail-current-message
+ (lambda () (mail-fetch-field field)))))
+
+ ;; MU4E SUPPORT
+
+(with-eval-after-load 'mu4e
+ ;; setup with (advice-add 'mu4e :after (lambda (&rest _) (autocrypt-mode)))
+
+ (defun autocrypt-mu4e-install ()
+ "Install autocrypt hooks for mu4e."
+ (add-hook 'mu4e-view-mode-hook #'autocrypt-process-header))
+
+ (defun autocrypt-mu4e-uninstall ()
+ "Remove autocrypt hooks for mu4e."
+ (remove-hook 'mu4e-view-mode-hook #'autocrypt-process-header))
+
+ (defun autocrypt-mu4e-header (field)
+ "Ask mu4e to return header field."
+ (save-window-excursion
+ (with-current-buffer (mu4e-view-raw-message)
+ (prog1 (mail-fetch-field field)
+ (kill-buffer (current-buffer)))))))
+
+ ;; MESSAGE SUPPORT
+
+(with-eval-after-load 'message
+ ;; setup with (add-hook 'message-mode-hook #'autocrypt-mode)
+
+ (defun autocrypt-message-install ()
+ "Install autocrypt hooks for message-mode."
+ (add-hook 'message-setup-hook #'autocrypt-message-setup)
+ (add-hook 'message-send-hook #'autocrypt-message-pre-send)
+ (define-key message-mode-map (kbd "C-c RET C-a")
#'autocrypt-message-setup))
+
+ (defun autocrypt-message-uninstall ()
+ "Remove autocrypt hooks for message-mode."
+ (remove-hook 'message-setup-hook #'autocrypt-message-setup)
+ (remove-hook 'message-send-hook #'autocrypt-message-pre-send)
+ (define-key message-mode-map (kbd "C-c RET C-a") nil))
+
+ (defun autocrypt-message-list-recipients ()
+ "Return a list of all recipients to this message."
+ (let (recipients)
+ (dolist (hdr '("To" "Cc" "Reply-To"))
+ (let* ((f (message-field-value hdr))
+ (r (and f (mail-extract-address-components f t))))
+ (setq recipients (nconc (mapcar #'cadr r) recipients))))
+ (delete-dups recipients)))
+
+ ;;
https://autocrypt.org/level1.html#key-gossip-injection-in-outbound-messages
+ (defun autocrypt-message-gossip-p (recipients)
+ "Find out if the current message should have gossip headers."
+ (and (mml-secure-is-encrypted-p)
+ (< 1 (length recipients))
+ (cl-every
+ (lambda (rec)
+ (let ((peer (cdr (assoc rec autocrypt-peers))))
+ (and peer (not (autocrypt-peer-deactivated peer)))))
+ recipients)))
+
+ (defun autocrypt-message-setup ()
+ "Check if Autocrypt is possible, and add pseudo headers."
+ (interactive)
+ (let ((recs (autocrypt-message-list-recipients))
+ (from (autocrypt-canonicalise (message-field-value "from"))))
+ ;; encrypt message if applicable
+ (save-excursion
+ (cl-case (autocrypt-recommendation from recs)
+ (available
+ (message-add-header "Do-Autocrypt: no"))
+ (discourage
+ (message-add-header "Do-Discouraged-Autocrypt: no"))))))
+
+ (defun autocrypt-message-pre-send ()
+ "Insert Autocrypt headers before sending a message.
+
+Will handle and remove \"Do-(Discourage-)Autocrypt\" if found."
+ (let* ((recs (autocrypt-message-list-recipients))
+ (from (autocrypt-canonicalise (message-field-value "from"))))
+ ;; encrypt message if applicable
+ (when (eq (autocrypt-recommendation from recs) 'encrypt)
+ (mml-secure-message-sign-encrypt "pgpmime"))
+ ;; check for manual autocrypt confirmations
+ (let ((do-autocrypt (message-fetch-field "Do-Autocrypt"))
+ (ddo-autocrypt (message-fetch-field "Do-Discouraged-Autocrypt"))
+ (query "Are you sure you want to use Autocrypt, even though it is
discouraged?"))
+ (when (and (not (mml-secure-is-encrypted-p))
+ (or (and do-autocrypt
+ (string= (downcase do-autocrypt) "yes"))
+ (and ddo-autocrypt
+ (string= (downcase ddo-autocrypt) "yes")
+ (yes-or-no-p query))))
+ (mml-secure-message-sign-encrypt "pgpmime")))
+ (message-remove-header "Do-Autocrypt")
+ (message-remove-header "Do-Discouraged-Autocrypt")
+ ;; insert gossip data
+ (when (autocrypt-message-gossip-p recs)
+ (let ((buf (generate-new-buffer " *autocrypt gossip*")))
+ (with-current-buffer buf
+ (dolist (addr (autocrypt-message-list-recipients))
+ (let ((header (autocrypt-generate-header addr t)))
+ (insert "Autocrypt-Gossip: " header "\n"))))
+ (mml-attach-buffer buf)
+ (mml-secure-part "pgpmime")
+ (add-hook 'message-send-hook
+ (lambda () (kill-buffer buf))
+ nil t)))
+ ;; insert autocrypt header
+ (let ((header (and from (autocrypt-generate-header from))))
+ (when header
+ (message-add-header (concat "Autocrypt: " header)))))))
+
+ ;; MINOR MODES
+
+;;;###autoload
+(define-minor-mode autocrypt-mode
+ "Enable Autocrypt support in current buffer.
+
+Behaviour shall adapt to current major mode. Should be added to
+the startup hook of your preferred MUA or mail-related major
+mode."
+ :group 'autocrypt
+ (if autocrypt-mode
+ (progn
+ (add-hook 'kill-emacs-hook #'autocrypt-save-data)
+ (autocrypt-load-data)
+ (autocrypt-mua-call :install))
+ (autocrypt-save-data)
+ (autocrypt-mua-call :uninstall)))
+
+(provide 'autocrypt)
+
+;;; autocrypt.el ends here
- [elpa] branch externals/autocrypt created (now a90aa6b644), ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 25635d8441 01/94: initial import,
ELPA Syncer <=
- [elpa] externals/autocrypt 374b5f2e5e 06/94: fixed section headers, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 50686561d7 09/94: added gitignore, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt ea9697885e 02/94: added readme, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 51a9463206 10/94: split autocrypt into multiple autoloaded files, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 4b87cc8cc1 12/94: fixed byte-compilation issues, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 283c9ade36 14/94: fixed checkdoc complaints, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt b263a7ddfa 18/94: replaced customize-set-variable with customize-save-variable, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt b712fbc71c 20/94: fixed missing preference bug, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt 71384d8f37 21/94: insert newline before inserting keydata, ELPA Syncer, 2023/03/26
- [elpa] externals/autocrypt e93e90febd 24/94: fixed autocrypt-compose-setup bind-check in message interface, ELPA Syncer, 2023/03/26