* canlock.el (canlock-sha1-function): Remove.
[gnus] / 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 'base64-encode-string "base64")
49 (autoload 'mail-fetch-field "mail-utils")
50 (defvar mail-header-separator)
51
52 (defgroup canlock nil
53   "The Cancel-Lock feature."
54   :group 'applications)
55
56 (defcustom canlock-password nil
57   "Password to use when signing a Cancel-Lock or a Cancel-Key header."
58   :type '(radio (const :format "Not specified " nil)
59                 (string :tag "Password" :size 0))
60   :group 'canlock)
61
62 (defcustom canlock-password-for-verify canlock-password
63   "Password to use when verifying a Cancel-Lock or a Cancel-Key header."
64   :type '(radio (const :format "Not specified " nil)
65                 (string :tag "Password" :size 0))
66   :group 'canlock)
67
68 (defcustom canlock-force-insert-header nil
69   "If non-nil, insert a Cancel-Lock or a Cancel-Key header even if the
70 buffer does not look like a news message."
71   :type 'boolean
72   :group 'canlock)
73
74 (eval-when-compile
75   (defmacro canlock-string-as-unibyte (string)
76     "Return a unibyte string with the same individual bytes as STRING."
77     (if (fboundp 'string-as-unibyte)
78         (list 'string-as-unibyte string)
79       string)))
80
81 (defun canlock-sha1 (message)
82   "Make a SHA-1 digest of MESSAGE as a unibyte string of length 20 bytes."
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
163                     "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
164                     control))
165               (setq id-for-lock nil)
166             (unless id-for-lock
167               (setq id-for-lock (mail-fetch-field "Message-ID"))))
168           (canlock-delete-headers)
169           (goto-char (point-max))))
170       (when news
171         (if (not (or id-for-key id-for-lock))
172             (message "There are no Message-ID(s)")
173           (unless password
174             (setq password (or canlock-password
175                                (read-passwd
176                                 "Password for Canlock: "))))
177           (if (or (not (stringp password)) (zerop (length password)))
178               (message "Password for Canlock is bad")
179             (setq key-for-key (when id-for-key
180                                 (canlock-make-cancel-key
181                                  id-for-key password))
182                   key-for-lock (when id-for-lock
183                                  (canlock-make-cancel-key
184                                   id-for-lock password)))
185             (if (not (or key-for-key key-for-lock))
186                 (message "Couldn't insert Canlock header")
187               (when key-for-key
188                 (insert "Cancel-Key: sha1:" key-for-key "\n"))
189               (when key-for-lock
190                 (insert "Cancel-Lock: sha1:"
191                         (base64-encode-string (canlock-sha1 key-for-lock))
192                         "\n")))))))))
193
194 ;;;###autoload
195 (defun canlock-verify (&optional buffer)
196   "Verify Cancel-Lock or Cancel-Key in BUFFER.
197 If BUFFER is nil, the current buffer is assumed.  Signal an error if
198 it fails."
199   (interactive)
200   (let (keys locks errmsg id-for-key id-for-lock password
201              key-for-key key-for-lock match)
202     (save-excursion
203       (when buffer
204         (set-buffer buffer))
205       (save-restriction
206         (widen)
207         (canlock-narrow-to-header)
208         (setq keys (canlock-fetch-fields 'key)
209               locks (canlock-fetch-fields))
210         (if (not (or keys locks))
211             (setq errmsg
212                   "There are neither Cancel-Lock nor Cancel-Key headers")
213           (setq id-for-key (canlock-fetch-id-for-key)
214                 id-for-lock (mail-fetch-field "Message-ID"))
215           (or id-for-key id-for-lock
216               (setq errmsg "There are no Message-ID(s)")))))
217     (if errmsg
218         (error "%s" errmsg)
219       (setq password (or canlock-password-for-verify
220                          (read-passwd "Password for Canlock: ")))
221       (if (or (not (stringp password)) (zerop (length password)))
222           (error "Password for Canlock is bad")
223         (when keys
224           (when id-for-key
225             (setq key-for-key (canlock-make-cancel-key id-for-key password))
226             (while (and keys (not match))
227               (setq match (string-equal key-for-key (pop keys)))))
228           (setq keys (if match "good" "bad")))
229         (setq match nil)
230         (when locks
231           (when id-for-lock
232             (setq key-for-lock
233                   (base64-encode-string
234                    (canlock-sha1 (canlock-make-cancel-key id-for-lock
235                                                           password))))
236             (when (and locks (not match))
237               (setq match (string-equal key-for-lock (pop locks)))))
238           (setq locks (if match "good" "bad")))
239         (prog1
240             (when (member "bad" (list keys locks))
241               "bad")
242           (cond ((and keys locks)
243                  (message "Cancel-Key is %s, Cancel-Lock is %s" keys locks))
244                 (locks
245                  (message "Cancel-Lock is %s" locks))
246                 (keys
247                  (message "Cancel-Key is %s" keys))))))))
248
249 (provide 'canlock)
250
251 ;;; canlock.el ends here