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