Refactor mml-smime.el, mml1991.el, mml2015.el
[gnus] / lisp / canlock.el
1 ;;; canlock.el --- functions for Cancel-Lock feature
2
3 ;; Copyright (C) 1998-1999, 2001-2016 Free Software Foundation, Inc.
4
5 ;; Author: Katsumi Yamaoka <yamaoka@jpl.org>
6 ;; Keywords: news, cancel-lock, hmac, sha1, rfc2104
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; Canlock is a library for generating and verifying Cancel-Lock and/or
26 ;; Cancel-Key header in news articles.  This is used to protect articles
27 ;; from rogue cancel, supersede or replace attacks.  The method is based
28 ;; on draft-ietf-usefor-cancel-lock-01.txt which was released on November
29 ;; 3rd 1998.  For instance, you can add Cancel-Lock (and possibly Cancel-
30 ;; Key) header in a news article by using a hook which will be evaluated
31 ;; just before sending an article as follows:
32 ;;
33 ;; (add-hook '*e**a*e-header-hook 'canlock-insert-header t)
34 ;;
35 ;; Verifying Cancel-Lock is mainly a function of news servers, however,
36 ;; you can verify your own article using the command `canlock-verify' in
37 ;; the (raw) article buffer.  You will be prompted for the password for
38 ;; each time if the option `canlock-password' or
39 ;; `canlock-password-for-verify' is nil.  Note that setting these
40 ;; options is a bit unsafe.
41
42 ;;; Code:
43
44 (eval-when-compile
45   (require 'cl))
46
47 (require 'sha1)
48
49 (defvar mail-header-separator)
50
51 (defgroup canlock nil
52   "The Cancel-Lock feature."
53   :group 'news)
54
55 (defcustom canlock-password nil
56   "Password to use when signing a Cancel-Lock or a Cancel-Key header."
57   :type '(radio (const :format "Not specified " nil)
58                 (string :tag "Password"))
59   :group 'canlock)
60
61 (defcustom canlock-password-for-verify canlock-password
62   "Password to use when verifying a Cancel-Lock or a Cancel-Key header."
63   :type '(radio (const :format "Not specified " nil)
64                 (string :tag "Password"))
65   :group 'canlock)
66
67 (defcustom canlock-force-insert-header nil
68   "If non-nil, insert a Cancel-Lock or a Cancel-Key header even if the
69 buffer does not look like a news message."
70   :type 'boolean
71   :group 'canlock)
72
73 (eval-when-compile
74   (defmacro canlock-string-as-unibyte (string)
75     "Return a unibyte string with the same individual bytes as STRING."
76     (if (fboundp 'string-as-unibyte)
77         (list 'string-as-unibyte string)
78       string)))
79
80 (defun canlock-sha1 (message)
81   "Make a SHA-1 digest of MESSAGE as a unibyte string of length 20 bytes."
82   (let (sha1-maximum-internal-length)
83     (sha1 message nil nil 'binary)))
84
85 (defun canlock-make-cancel-key (message-id password)
86   "Make a Cancel-Key header."
87   (when (> (length password) 20)
88     (setq password (canlock-sha1 password)))
89   (setq password (concat password (make-string (- 64 (length password)) 0)))
90   (let ((ipad (mapconcat (lambda (byte)
91                            (char-to-string (logxor 54 byte)))
92                          password ""))
93         (opad (mapconcat (lambda (byte)
94                            (char-to-string (logxor 92 byte)))
95                          password "")))
96     (base64-encode-string
97      (canlock-sha1
98       (concat opad
99               (canlock-sha1
100                (concat ipad (canlock-string-as-unibyte message-id))))))))
101
102 (defun canlock-narrow-to-header ()
103   "Narrow the buffer to the head of the message."
104   (let (case-fold-search)
105     (narrow-to-region
106      (goto-char (point-min))
107      (goto-char (if (re-search-forward
108                      (format "^$\\|^%s$"
109                              (regexp-quote mail-header-separator))
110                      nil t)
111                     (match-beginning 0)
112                   (point-max))))))
113
114 (defun canlock-delete-headers ()
115   "Delete Cancel-Key or Cancel-Lock headers in the narrowed buffer."
116   (let ((case-fold-search t))
117     (goto-char (point-min))
118     (while (re-search-forward "^Cancel-\\(Key\\|Lock\\):" nil t)
119       (delete-region (match-beginning 0)
120                      (if (re-search-forward "^[^\t ]" nil t)
121                          (goto-char (match-beginning 0))
122                        (point-max))))))
123
124 (defun canlock-fetch-fields (&optional key)
125   "Return a list of the values of Cancel-Lock header.
126 If KEY is non-nil, look for a Cancel-Key header instead.  The buffer
127 is expected to be narrowed to just the headers of the message."
128   (let ((field (mail-fetch-field (if key "Cancel-Key" "Cancel-Lock")))
129         fields rest
130         (case-fold-search t))
131     (when field
132       (setq fields (split-string field "[\t\n\r ,]+"))
133       (while fields
134         (when (string-match "^sha1:" (setq field (pop fields)))
135           (push (substring field 5) rest)))
136       (nreverse rest))))
137
138 (defun canlock-fetch-id-for-key ()
139   "Return a Message-ID in Cancel, Supersedes or Replaces header.
140 The buffer is expected to be narrowed to just the headers of the
141 message."
142   (or (let ((cancel (mail-fetch-field "Control")))
143         (and cancel
144              (string-match "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
145                            cancel)
146              (match-string 1 cancel)))
147       (mail-fetch-field "Supersedes")
148       (mail-fetch-field "Replaces")))
149
150 ;;;###autoload
151 (defun canlock-insert-header (&optional id-for-key id-for-lock password)
152   "Insert a Cancel-Key and/or a Cancel-Lock header if possible."
153   (let (news control key-for-key key-for-lock)
154     (save-excursion
155       (save-restriction
156         (canlock-narrow-to-header)
157         (when (setq news (or canlock-force-insert-header
158                              (mail-fetch-field "Newsgroups")))
159           (unless id-for-key
160             (setq id-for-key (canlock-fetch-id-for-key)))
161           (if (and (setq control (mail-fetch-field "Control"))
162                    (string-match "^cancel[\t ]+<[^\t\n @<>]+@[^\t\n @<>]+>"
163                                  control))
164               (setq id-for-lock nil)
165             (unless id-for-lock
166               (setq id-for-lock (mail-fetch-field "Message-ID"))))
167           (canlock-delete-headers)
168           (goto-char (point-max))))
169       (when news
170         (if (not (or id-for-key id-for-lock))
171             (message "There are no Message-ID(s)")
172           (unless password
173             (setq password (or canlock-password
174                                (read-passwd
175                                 "Password for Canlock: "))))
176           (if (or (not (stringp password)) (zerop (length password)))
177               (message "Password for Canlock is bad")
178             (setq key-for-key (when id-for-key
179                                 (canlock-make-cancel-key
180                                  id-for-key password))
181                   key-for-lock (when id-for-lock
182                                  (canlock-make-cancel-key
183                                   id-for-lock password)))
184             (if (not (or key-for-key key-for-lock))
185                 (message "Couldn't insert Canlock header")
186               (when key-for-key
187                 (insert "Cancel-Key: sha1:" key-for-key "\n"))
188               (when key-for-lock
189                 (insert "Cancel-Lock: sha1:"
190                         (base64-encode-string (canlock-sha1 key-for-lock))
191                         "\n")))))))))
192
193 ;;;###autoload
194 (defun canlock-verify (&optional buffer)
195   "Verify Cancel-Lock or Cancel-Key in BUFFER.
196 If BUFFER is nil, the current buffer is assumed.  Signal an error if
197 it fails."
198   (interactive)
199   (let (keys locks errmsg id-for-key id-for-lock password
200              key-for-key key-for-lock match)
201     (save-excursion
202       (when buffer
203         (set-buffer buffer))
204       (save-restriction
205         (widen)
206         (canlock-narrow-to-header)
207         (setq keys (canlock-fetch-fields 'key)
208               locks (canlock-fetch-fields))
209         (if (not (or keys locks))
210             (setq errmsg
211                   "There are neither Cancel-Lock nor Cancel-Key headers")
212           (setq id-for-key (canlock-fetch-id-for-key)
213                 id-for-lock (mail-fetch-field "Message-ID"))
214           (or id-for-key id-for-lock
215               (setq errmsg "There are no Message-ID(s)")))))
216     (if errmsg
217         (error "%s" errmsg)
218       (setq password (or canlock-password-for-verify
219                          (read-passwd "Password for Canlock: ")))
220       (if (or (not (stringp password)) (zerop (length password)))
221           (error "Password for Canlock is bad")
222         (when keys
223           (when id-for-key
224             (setq key-for-key (canlock-make-cancel-key id-for-key password))
225             (while (and keys (not match))
226               (setq match (string-equal key-for-key (pop keys)))))
227           (setq keys (if match "good" "bad")))
228         (setq match nil)
229         (when locks
230           (when id-for-lock
231             (setq key-for-lock
232                   (base64-encode-string
233                    (canlock-sha1 (canlock-make-cancel-key id-for-lock
234                                                           password))))
235             (when (and locks (not match))
236               (setq match (string-equal key-for-lock (pop locks)))))
237           (setq locks (if match "good" "bad")))
238         (prog1
239             (when (member "bad" (list keys locks))
240               "bad")
241           (cond ((and keys locks)
242                  (message "Cancel-Key is %s, Cancel-Lock is %s" keys locks))
243                 (locks
244                  (message "Cancel-Lock is %s" locks))
245                 (keys
246                  (message "Cancel-Key is %s" keys))))))))
247
248 (provide 'canlock)
249
250 ;;; canlock.el ends here