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