(mail-fetch-field): Autoload it (fix xemacs compile
[gnus] / lisp / canlock.el
1 ;;; canlock.el --- functions for Cancel-Lock feature
2
3 ;; Copyright (C) 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc.
4
5 ;; Author: Katsumi Yamaoka <yamaoka@jpl.org>
6 ;; Keywords: news, cancel-lock, hmac, sha1, rfc2104
7
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; see the file COPYING.  If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
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 `canlock-password-for-
39 ;; verify' is nil.  Note that setting these options is a bit unsafe.
40
41 ;;; Code:
42
43 (eval-when-compile
44   (require 'cl))
45
46 (autoload 'sha1-binary "sha1-el")
47 (autoload 'base64-encode-string "base64")
48 (autoload 'mail-fetch-field "mail-utils")
49
50 (defgroup canlock nil
51   "The Cancel-Lock feature."
52   :group 'applications)
53
54 (defcustom canlock-sha1-function 'sha1-binary
55   "Function to call to make a SHA-1 message digest."
56   :type '(radio (function-item sha1-binary)
57                 (function-item canlock-sha1-with-openssl)
58                 (function :tag "Other"))
59   :group 'canlock)
60
61 (defcustom canlock-sha1-function-for-verify canlock-sha1-function
62   "Function to call to make a SHA-1 message digest for verifying."
63   :type '(radio (function-item sha1-binary)
64                 (function-item canlock-sha1-with-openssl)
65                 (function :tag "Other"))
66   :group 'canlock)
67
68 (defcustom canlock-openssl-program "openssl"
69   "Name of OpenSSL program."
70   :type 'string
71   :group 'canlock)
72
73 (defcustom canlock-openssl-args '("sha1")
74   "Arguments passed to the OpenSSL program."
75   :type 'sexp
76   :group 'canlock)
77
78 (defcustom canlock-ignore-errors nil
79   "If non-nil, ignore any error signals."
80   :type 'boolean
81   :group 'canlock)
82
83 (defcustom canlock-password nil
84   "Password to use when signing a Cancel-Lock or a Cancel-Key header."
85   :type 'string
86   :group 'canlock)
87
88 (defcustom canlock-password-for-verify canlock-password
89   "Password to use when verifying a Cancel-Lock or a Cancel-Key header."
90   :type 'string
91   :group 'canlock)
92
93 (defcustom canlock-force-insert-header nil
94   "If non-nil, insert a Cancel-Lock or a Cancel-Key header even if the
95 buffer does not look like a news message."
96   :type 'boolean
97   :group 'canlock)
98
99 (defun canlock-sha1-with-openssl (message)
100   "Make a SHA-1 digest of MESSAGE using OpenSSL."
101   (let (default-enable-multibyte-characters)
102     (with-temp-buffer
103       (let ((coding-system-for-read 'binary)
104             (coding-system-for-write 'binary)
105             selective-display
106             (case-fold-search t))
107         (insert message)
108         (apply 'call-process-region (point-min) (point-max)
109                canlock-openssl-program t t nil canlock-openssl-args)
110         (goto-char (point-min))
111         (insert "\"")
112         (while (re-search-forward "\\([0-9a-f][0-9a-f]\\)" nil t)
113           (replace-match "\\\\x\\1"))
114         (insert "\"")
115         (goto-char (point-min))
116         (read (current-buffer))))))
117
118 (eval-when-compile
119   (defmacro canlock-string-as-unibyte (string)
120     "Return a unibyte string with the same individual bytes as STRING."
121     (if (fboundp 'string-as-unibyte)
122         (list 'string-as-unibyte string)
123       string)))
124
125 (defun canlock-sha1 (message)
126   "Make a SHA-1 digest of MESSAGE as a unibyte string of length 20 bytes."
127   (canlock-string-as-unibyte (funcall canlock-sha1-function message)))
128
129 (defun canlock-make-cancel-key (message-id password)
130   "Make a Cancel-Key header."
131   (when (> (length password) 20)
132     (setq password (canlock-sha1 password)))
133   (setq password (concat password (make-string (- 64 (length password)) 0)))
134   (let ((ipad (mapconcat (lambda (byte)
135                            (char-to-string (logxor 54 byte)))
136                          password ""))
137         (opad (mapconcat (lambda (byte)
138                            (char-to-string (logxor 92 byte)))
139                          password "")))
140     (base64-encode-string
141      (canlock-sha1
142       (concat opad
143               (canlock-sha1
144                (concat ipad (canlock-string-as-unibyte message-id))))))))
145
146 (defun canlock-narrow-to-header ()
147   "Narrow the buffer to the head of the message."
148   (let (case-fold-search)
149     (narrow-to-region
150      (goto-char (point-min))
151      (goto-char (if (re-search-forward
152                      (format "^$\\|^%s$"
153                              (regexp-quote mail-header-separator))
154                      nil t)
155                     (match-beginning 0)
156                   (point-max))))))
157
158 (defun canlock-delete-headers ()
159   "Delete Cancel-Key or Cancel-Lock headers in the narrowed buffer."
160   (let ((case-fold-search t))
161     (goto-char (point-min))
162     (while (re-search-forward "^Cancel-\\(Key\\|Lock\\):" nil t)
163       (delete-region (match-beginning 0)
164                      (if (re-search-forward "^[^\t ]" nil t)
165                          (goto-char (match-beginning 0))
166                        (point-max))))))
167
168 (defun canlock-fetch-fields (&optional key)
169   "Return a list of the values of Cancel-Lock header.
170 If KEY is non-nil, look for a Cancel-Key header instead.  The buffer
171 is expected to be narrowed to just the headers of the message."
172   (let ((field (mail-fetch-field (if key "Cancel-Key" "Cancel-Lock")))
173         fields rest
174         (case-fold-search t))
175     (when field
176       (setq fields (split-string field "[\t\n\r ,]+"))
177       (while fields
178         (when (string-match "^sha1:" (setq field (pop fields)))
179           (push (substring field 5) rest)))
180       (nreverse rest))))
181
182 (defun canlock-fetch-id-for-key ()
183   "Return a Message-ID in Cancel, Supersedes or Replaces header.
184 The buffer is expected to be narrowed to just the headers of the
185 message."
186   (or (let ((cancel (mail-fetch-field "Control")))
187         (and cancel
188              (string-match "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
189                            cancel)
190              (match-string 1 cancel)))
191       (mail-fetch-field "Supersedes")
192       (mail-fetch-field "Replaces")))
193
194 ;;;###autoload
195 (defun canlock-insert-header (&optional id-for-key id-for-lock password)
196   "Insert a Cancel-Key and/or a Cancel-Lock header if possible."
197   (let (news control key-for-key key-for-lock)
198     (save-excursion
199       (save-restriction
200         (canlock-narrow-to-header)
201         (when (setq news (or canlock-force-insert-header
202                              (mail-fetch-field "Newsgroups")))
203           (unless id-for-key
204             (setq id-for-key (canlock-fetch-id-for-key)))
205           (if (and (setq control (mail-fetch-field "Control"))
206                    (string-match
207                     "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
208                     control))
209               (setq id-for-lock nil)
210             (unless id-for-lock
211               (setq id-for-lock (mail-fetch-field "Message-ID"))))
212           (canlock-delete-headers)
213           (goto-char (point-max))))
214       (when news
215         (if (not (or id-for-key id-for-lock))
216             (message "There are no Message-ID(s)")
217           (unless password
218             (setq password (or canlock-password
219                                (read-passwd
220                                 "Password for Canlock: "))))
221           (if (or (not (stringp password)) (zerop (length password)))
222               (message "Password for Canlock is bad")
223             (setq key-for-key (when id-for-key
224                                 (canlock-make-cancel-key
225                                  id-for-key password))
226                   key-for-lock (when id-for-lock
227                                  (canlock-make-cancel-key
228                                   id-for-lock password)))
229             (if (not (or key-for-key key-for-lock))
230                 (message "Couldn't insert Canlock header")
231               (when key-for-key
232                 (insert "Cancel-Key: sha1:" key-for-key "\n"))
233               (when key-for-lock
234                 (insert "Cancel-Lock: sha1:"
235                         (base64-encode-string (canlock-sha1 key-for-lock))
236                         "\n")))))))))
237
238 ;;;###autoload
239 (defun canlock-verify (&optional buffer)
240   "Verify Cancel-Lock or Cancel-Key in BUFFER.
241 If BUFFER is nil, the current buffer is assumed.  Signal an error if
242 it fails.  You can modify the behavior of this function to return non-
243 nil instead of to signal an error by setting the option
244 `canlock-ignore-errors' to non-nil."
245   (interactive)
246   (let ((canlock-sha1-function (or canlock-sha1-function-for-verify
247                                    canlock-sha1-function))
248         keys locks errmsg id-for-key id-for-lock password
249         key-for-key key-for-lock match)
250     (save-excursion
251       (when buffer
252         (set-buffer buffer))
253       (save-restriction
254         (widen)
255         (canlock-narrow-to-header)
256         (setq keys (canlock-fetch-fields 'key)
257               locks (canlock-fetch-fields))
258         (if (not (or keys locks))
259             (setq errmsg
260                   "There are neither Cancel-Lock nor Cancel-Key headers")
261           (setq id-for-key (canlock-fetch-id-for-key)
262                 id-for-lock (mail-fetch-field "Message-ID"))
263           (or id-for-key id-for-lock
264               (setq errmsg "There are no Message-ID(s)")))))
265
266     (if errmsg
267         (if canlock-ignore-errors
268             errmsg
269           (error "%s" errmsg))
270
271       (setq password (or canlock-password-for-verify
272                          (read-passwd "Password for Canlock: ")))
273       (if (or (not (stringp password)) (zerop (length password)))
274           (progn
275             (setq errmsg "Password for Canlock is bad")
276             (if canlock-ignore-errors
277                 errmsg
278               (error "%s" errmsg)))
279
280         (when keys
281           (when id-for-key
282             (setq key-for-key (canlock-make-cancel-key id-for-key password))
283             (while (and keys (not match))
284               (setq match (string-equal key-for-key (pop keys)))))
285           (setq keys (if match "good" "bad")))
286         (setq match nil)
287
288         (when locks
289           (when id-for-lock
290             (setq key-for-lock
291                   (base64-encode-string
292                    (canlock-sha1 (canlock-make-cancel-key id-for-lock
293                                                           password))))
294             (when (and locks (not match))
295               (setq match (string-equal key-for-lock (pop locks)))))
296           (setq locks (if match "good" "bad")))
297
298         (prog1
299             (when (member "bad" (list keys locks))
300               "bad")
301           (cond ((and keys locks)
302                  (message "Cancel-Key is %s, Cancel-Lock is %s" keys locks))
303                 (locks
304                  (message "Cancel-Lock is %s" locks))
305                 (keys
306                  (message "Cancel-Key is %s" keys))))))))
307
308 (provide 'canlock)
309
310 ;;; canlock.el ends here