1 ;;; canlock.el --- functions for Cancel-Lock feature
3 ;; Copyright (C) 1998, 1999, 2001, 2002 Free Software Foundation, Inc.
5 ;; Author: Katsumi Yamaoka <yamaoka@jpl.org>
6 ;; Keywords: news, cancel-lock, hmac, sha1, rfc2104
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)
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.
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.
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:
33 ;; (add-hook '*e**a*e-header-hook 'canlock-insert-header t)
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.
43 (defconst canlock-version "0.8")
48 (autoload 'sha1-binary "sha1-el")
49 (autoload 'base64-encode-string "base64")
52 "The Cancel-Lock feature."
55 (defcustom canlock-sha1-function 'sha1-binary
56 "Function to call to make a SHA-1 message digest."
57 :type '(radio (function-item sha1-binary)
58 (function-item canlock-sha1-with-openssl)
59 (function :tag "Other"))
62 (defcustom canlock-sha1-function-for-verify canlock-sha1-function
63 "Function to call to make a SHA-1 message digest for verifying."
64 :type '(radio (function-item sha1-binary)
65 (function-item canlock-sha1-with-openssl)
66 (function :tag "Other"))
69 (defcustom canlock-openssl-program "openssl"
70 "Name of OpenSSL program."
74 (defcustom canlock-openssl-args '("sha1")
75 "Arguments passed to the OpenSSL program."
79 (defcustom canlock-ignore-errors nil
80 "If non-nil, ignore any error signals."
84 (defcustom canlock-password nil
85 "Password to use when signing a Cancel-Lock or a Cancel-Key header."
89 (defcustom canlock-password-for-verify canlock-password
90 "Password to use when verifying a Cancel-Lock or a Cancel-Key header."
94 (defcustom canlock-force-insert-header nil
95 "If non-nil, insert a Cancel-Lock or a Cancel-Key header even if the
96 buffer does not look like a news message."
101 (defmacro canlock-string-as-unibyte (string)
102 "Return a unibyte string with the same individual bytes as STRING."
103 (if (fboundp 'string-as-unibyte)
104 (list 'string-as-unibyte string)
107 (defun canlock-sha1-with-openssl (message)
108 "Make a SHA-1 digest of MESSAGE using OpenSSL."
109 (let (default-enable-multibyte-characters)
111 (let ((coding-system-for-read 'binary)
112 (coding-system-for-write 'binary)
114 (case-fold-search t))
116 (apply 'call-process-region (point-min) (point-max)
117 canlock-openssl-program t t nil canlock-openssl-args)
118 (goto-char (point-min))
120 (while (re-search-forward "[0-9a-f][0-9a-f]" nil t)
121 (replace-match (concat "\\\\x" (match-string 0))))
123 (goto-char (point-min))
124 (canlock-string-as-unibyte (read (current-buffer)))))))
126 (defvar canlock-read-passwd nil)
127 (defun canlock-read-passwd (prompt &rest args)
128 "Read a password using PROMPT.
129 If ARGS, PROMPT is used as an argument to `format'."
132 (apply 'format prompt args)
134 (unless canlock-read-passwd
135 (if (or (fboundp 'read-passwd) (load "passwd" t))
136 (setq canlock-read-passwd 'read-passwd)
137 (unless (fboundp 'ange-ftp-read-passwd)
138 (autoload 'ange-ftp-read-passwd "ange-ftp"))
139 (setq canlock-read-passwd 'ange-ftp-read-passwd)))
140 (funcall canlock-read-passwd prompt)))
142 (defun canlock-make-cancel-key (message-id password)
143 "Make a Cancel-Key header."
144 (cond ((> (length password) 20)
145 (setq password (funcall canlock-sha1-function password)))
146 ((< (length password) 20)
147 (setq password (concat
149 (make-string (- 20 (length password)) 0)))))
150 (setq password (concat password (make-string 44 0)))
151 (let ((ipad (mapconcat (lambda (char)
152 (char-to-string (logxor 54 char)))
154 (opad (mapconcat (lambda (char)
155 (char-to-string (logxor 92 char)))
157 (base64-encode-string
158 (funcall canlock-sha1-function
161 (funcall canlock-sha1-function
163 (canlock-string-as-unibyte message-id))))))))
165 (defun canlock-narrow-to-header ()
166 "Narrow the buffer to the head of the message."
167 (let (case-fold-search)
169 (goto-char (point-min))
170 (goto-char (if (re-search-forward
172 (regexp-quote mail-header-separator))
177 (defun canlock-delete-headers ()
178 "Delete Cancel-Key or Cancel-Lock headers in the narrowed buffer."
179 (let ((case-fold-search t))
180 (goto-char (point-min))
181 (while (re-search-forward "^Cancel-\\(Key\\|Lock\\):" nil t)
182 (delete-region (match-beginning 0)
183 (if (re-search-forward "^[^\t ]" nil t)
184 (goto-char (match-beginning 0))
187 (defun canlock-fetch-fields (&optional key)
188 "Return a list of the values of Cancel-Lock header.
189 If KEY is non-nil, look for a Cancel-Key header instead. The buffer
190 is expected to be narrowed to just the headers of the message."
191 (let ((field (mail-fetch-field (if key "Cancel-Key" "Cancel-Lock")))
193 (case-fold-search t))
195 (setq fields (split-string field "[\t\n\r ,]+"))
197 (when (string-match "^sha1:" (setq field (pop fields)))
198 (push (substring field 5) rest)))
201 (defun canlock-fetch-id-for-key ()
202 "Return a Message-ID in Cancel, Supersedes or Replaces header.
203 The buffer is expected to be narrowed to just the headers of the
205 (or (let ((cancel (mail-fetch-field "Control")))
207 (string-match "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
209 (match-string 1 cancel)))
210 (mail-fetch-field "Supersedes")
211 (mail-fetch-field "Replaces")))
214 (defun canlock-insert-header (&optional id-for-key id-for-lock password)
215 "Insert a Cancel-Key and/or a Cancel-Lock header if possible."
216 (let (news control key-for-key key-for-lock)
219 (canlock-narrow-to-header)
220 (when (setq news (or canlock-force-insert-header
221 (mail-fetch-field "Newsgroups")))
223 (setq id-for-key (canlock-fetch-id-for-key)))
224 (if (and (setq control (mail-fetch-field "Control"))
226 "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
228 (setq id-for-lock nil)
230 (setq id-for-lock (mail-fetch-field "Message-ID"))))
231 (canlock-delete-headers)
232 (goto-char (point-max))))
234 (if (not (or id-for-key id-for-lock))
235 (message "There are no Message-ID(s)")
237 (setq password (or canlock-password
239 "Password for Canlock: "))))
240 (if (or (not (stringp password)) (zerop (length password)))
241 (message "Password for Canlock is bad")
242 (setq key-for-key (when id-for-key
243 (canlock-make-cancel-key
244 id-for-key password))
245 key-for-lock (when id-for-lock
246 (canlock-make-cancel-key
247 id-for-lock password)))
248 (if (not (or key-for-key key-for-lock))
249 (message "Couldn't insert Canlock header")
251 (insert "Cancel-Key: sha1:" key-for-key "\n"))
253 (insert "Cancel-Lock: sha1:"
254 (base64-encode-string (funcall canlock-sha1-function
259 (defun canlock-verify (&optional buffer)
260 "Verify Cancel-Lock or Cancel-Key in BUFFER.
261 If BUFFER is nil, the current buffer is assumed. Signal an error if
262 it fails. You can modify the behavior of this function to return non-
263 nil instead of to signal an error by setting the option
264 `canlock-ignore-errors' to non-nil."
266 (let ((canlock-sha1-function (or canlock-sha1-function-for-verify
267 canlock-sha1-function))
268 keys locks errmsg id-for-key id-for-lock password
269 key-for-key key-for-lock match)
275 (canlock-narrow-to-header)
276 (setq keys (canlock-fetch-fields 'key)
277 locks (canlock-fetch-fields))
278 (if (not (or keys locks))
280 "There are neither Cancel-Lock nor Cancel-Key headers")
281 (setq id-for-key (canlock-fetch-id-for-key)
282 id-for-lock (mail-fetch-field "Message-ID"))
283 (or id-for-key id-for-lock
284 (setq errmsg "There are no Message-ID(s)")))))
287 (if canlock-ignore-errors
291 (setq password (or canlock-password-for-verify
292 (canlock-read-passwd "Password for Canlock: ")))
293 (if (or (not (stringp password)) (zerop (length password)))
295 (setq errmsg "Password for Canlock is bad")
296 (if canlock-ignore-errors
298 (error "%s" errmsg)))
302 (setq key-for-key (canlock-make-cancel-key id-for-key password))
303 (while (and keys (not match))
304 (setq match (string-equal key-for-key (pop keys)))))
305 (setq keys (if match "good" "bad")))
311 (base64-encode-string (funcall canlock-sha1-function
312 (canlock-make-cancel-key
313 id-for-lock password))))
314 (when (and locks (not match))
315 (setq match (string-equal key-for-lock (pop locks)))))
316 (setq locks (if match "good" "bad")))
319 (when (member "bad" (list keys locks))
321 (cond ((and keys locks)
322 (message "Cancel-Key is %s, Cancel-Lock is %s" keys locks))
324 (message "Cancel-Lock is %s" locks))
326 (message "Cancel-Key is %s" keys))))))))
330 ;;; canlock.el ends here