file uu-decode.pbm was added on branch t-gnus-6_17 on 2005-05-01 23:25:48 +0000
[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 (eval-and-compile
48   (require 'sha1-el)
49   (condition-case nil
50       (sha1 "" nil nil 'binary)
51     (wrong-number-of-arguments
52      (let ((mel (locate-library "mel")))
53        (when mel
54          (load (expand-file-name "sha1-el" (file-name-directory mel))
55                nil t))))))
56
57 (defvar mail-header-separator)
58
59 (defgroup canlock nil
60   "The Cancel-Lock feature."
61   :group 'applications)
62
63 (defcustom canlock-password nil
64   "Password to use when signing 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-password-for-verify canlock-password
70   "Password to use when verifying a Cancel-Lock or a Cancel-Key header."
71   :type '(radio (const :format "Not specified " nil)
72                 (string :tag "Password" :size 0))
73   :group 'canlock)
74
75 (defcustom canlock-force-insert-header nil
76   "If non-nil, insert a Cancel-Lock or a Cancel-Key header even if the
77 buffer does not look like a news message."
78   :type 'boolean
79   :group 'canlock)
80
81 (eval-when-compile
82   (defmacro canlock-string-as-unibyte (string)
83     "Return a unibyte string with the same individual bytes as STRING."
84     (if (fboundp 'string-as-unibyte)
85         (list 'string-as-unibyte string)
86       string)))
87
88 (defun canlock-sha1 (message)
89   "Make a SHA-1 digest of MESSAGE as a unibyte string of length 20 bytes."
90   (condition-case nil
91       (let (sha1-maximum-internal-length)
92         (sha1 message nil nil 'binary))
93     (wrong-number-of-arguments
94      (canlock-string-as-unibyte (sha1-binary message)))))
95
96 (defun canlock-make-cancel-key (message-id password)
97   "Make a Cancel-Key header."
98   (when (> (length password) 20)
99     (setq password (canlock-sha1 password)))
100   (setq password (concat password (make-string (- 64 (length password)) 0)))
101   (let ((ipad (mapconcat (lambda (byte)
102                            (char-to-string (logxor 54 byte)))
103                          password ""))
104         (opad (mapconcat (lambda (byte)
105                            (char-to-string (logxor 92 byte)))
106                          password "")))
107     (base64-encode-string
108      (canlock-sha1
109       (concat opad
110               (canlock-sha1
111                (concat ipad (canlock-string-as-unibyte message-id))))))))
112
113 (defun canlock-narrow-to-header ()
114   "Narrow the buffer to the head of the message."
115   (let (case-fold-search)
116     (narrow-to-region
117      (goto-char (point-min))
118      (goto-char (if (re-search-forward
119                      (format "^$\\|^%s$"
120                              (regexp-quote mail-header-separator))
121                      nil t)
122                     (match-beginning 0)
123                   (point-max))))))
124
125 (defun canlock-delete-headers ()
126   "Delete Cancel-Key or Cancel-Lock headers in the narrowed buffer."
127   (let ((case-fold-search t))
128     (goto-char (point-min))
129     (while (re-search-forward "^Cancel-\\(Key\\|Lock\\):" nil t)
130       (delete-region (match-beginning 0)
131                      (if (re-search-forward "^[^\t ]" nil t)
132                          (goto-char (match-beginning 0))
133                        (point-max))))))
134
135 (defun canlock-fetch-fields (&optional key)
136   "Return a list of the values of Cancel-Lock header.
137 If KEY is non-nil, look for a Cancel-Key header instead.  The buffer
138 is expected to be narrowed to just the headers of the message."
139   (let ((field (mail-fetch-field (if key "Cancel-Key" "Cancel-Lock")))
140         fields rest
141         (case-fold-search t))
142     (when field
143       (setq fields (split-string field "[\t\n\r ,]+"))
144       (while fields
145         (when (string-match "^sha1:" (setq field (pop fields)))
146           (push (substring field 5) rest)))
147       (nreverse rest))))
148
149 (defun canlock-fetch-id-for-key ()
150   "Return a Message-ID in Cancel, Supersedes or Replaces header.
151 The buffer is expected to be narrowed to just the headers of the
152 message."
153   (or (let ((cancel (mail-fetch-field "Control")))
154         (and cancel
155              (string-match "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
156                            cancel)
157              (match-string 1 cancel)))
158       (mail-fetch-field "Supersedes")
159       (mail-fetch-field "Replaces")))
160
161 ;;;###autoload
162 (defun canlock-insert-header (&optional id-for-key id-for-lock password)
163   "Insert a Cancel-Key and/or a Cancel-Lock header if possible."
164   (let (news control key-for-key key-for-lock)
165     (save-excursion
166       (save-restriction
167         (canlock-narrow-to-header)
168         (when (setq news (or canlock-force-insert-header
169                              (mail-fetch-field "Newsgroups")))
170           (unless id-for-key
171             (setq id-for-key (canlock-fetch-id-for-key)))
172           (if (and (setq control (mail-fetch-field "Control"))
173                    (string-match "^cancel[\t ]+<[^\t\n @<>]+@[^\t\n @<>]+>"
174                                  control))
175               (setq id-for-lock nil)
176             (unless id-for-lock
177               (setq id-for-lock (mail-fetch-field "Message-ID"))))
178           (canlock-delete-headers)
179           (goto-char (point-max))))
180       (when news
181         (if (not (or id-for-key id-for-lock))
182             (message "There are no Message-ID(s)")
183           (unless password
184             (setq password (or canlock-password
185                                (read-passwd
186                                 "Password for Canlock: "))))
187           (if (or (not (stringp password)) (zerop (length password)))
188               (message "Password for Canlock is bad")
189             (setq key-for-key (when id-for-key
190                                 (canlock-make-cancel-key
191                                  id-for-key password))
192                   key-for-lock (when id-for-lock
193                                  (canlock-make-cancel-key
194                                   id-for-lock password)))
195             (if (not (or key-for-key key-for-lock))
196                 (message "Couldn't insert Canlock header")
197               (when key-for-key
198                 (insert "Cancel-Key: sha1:" key-for-key "\n"))
199               (when key-for-lock
200                 (insert "Cancel-Lock: sha1:"
201                         (base64-encode-string (canlock-sha1 key-for-lock))
202                         "\n")))))))))
203
204 ;;;###autoload
205 (defun canlock-verify (&optional buffer)
206   "Verify Cancel-Lock or Cancel-Key in BUFFER.
207 If BUFFER is nil, the current buffer is assumed.  Signal an error if
208 it fails."
209   (interactive)
210   (let (keys locks errmsg id-for-key id-for-lock password
211              key-for-key key-for-lock match)
212     (save-excursion
213       (when buffer
214         (set-buffer buffer))
215       (save-restriction
216         (widen)
217         (canlock-narrow-to-header)
218         (setq keys (canlock-fetch-fields 'key)
219               locks (canlock-fetch-fields))
220         (if (not (or keys locks))
221             (setq errmsg
222                   "There are neither Cancel-Lock nor Cancel-Key headers")
223           (setq id-for-key (canlock-fetch-id-for-key)
224                 id-for-lock (mail-fetch-field "Message-ID"))
225           (or id-for-key id-for-lock
226               (setq errmsg "There are no Message-ID(s)")))))
227     (if errmsg
228         (error "%s" errmsg)
229       (setq password (or canlock-password-for-verify
230                          (read-passwd "Password for Canlock: ")))
231       (if (or (not (stringp password)) (zerop (length password)))
232           (error "Password for Canlock is bad")
233         (when keys
234           (when id-for-key
235             (setq key-for-key (canlock-make-cancel-key id-for-key password))
236             (while (and keys (not match))
237               (setq match (string-equal key-for-key (pop keys)))))
238           (setq keys (if match "good" "bad")))
239         (setq match nil)
240         (when locks
241           (when id-for-lock
242             (setq key-for-lock
243                   (base64-encode-string
244                    (canlock-sha1 (canlock-make-cancel-key id-for-lock
245                                                           password))))
246             (when (and locks (not match))
247               (setq match (string-equal key-for-lock (pop locks)))))
248           (setq locks (if match "good" "bad")))
249         (prog1
250             (when (member "bad" (list keys locks))
251               "bad")
252           (cond ((and keys locks)
253                  (message "Cancel-Key is %s, Cancel-Lock is %s" keys locks))
254                 (locks
255                  (message "Cancel-Lock is %s" locks))
256                 (keys
257                  (message "Cancel-Key is %s" keys))))))))
258
259 (provide 'canlock)
260
261 ;;; canlock.el ends here