* nnmaildir.el (nnmaildir-request-set-mark): Handle the "too many
[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 (require 'sha1-el)
48
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   (let (sha1-maximum-internal-length)
84     (sha1 message nil nil 'binary)))
85
86 (defun canlock-make-cancel-key (message-id password)
87   "Make a Cancel-Key header."
88   (when (> (length password) 20)
89     (setq password (canlock-sha1 password)))
90   (setq password (concat password (make-string (- 64 (length password)) 0)))
91   (let ((ipad (mapconcat (lambda (byte)
92                            (char-to-string (logxor 54 byte)))
93                          password ""))
94         (opad (mapconcat (lambda (byte)
95                            (char-to-string (logxor 92 byte)))
96                          password "")))
97     (base64-encode-string
98      (canlock-sha1
99       (concat opad
100               (canlock-sha1
101                (concat ipad (canlock-string-as-unibyte message-id))))))))
102
103 (defun canlock-narrow-to-header ()
104   "Narrow the buffer to the head of the message."
105   (let (case-fold-search)
106     (narrow-to-region
107      (goto-char (point-min))
108      (goto-char (if (re-search-forward
109                      (format "^$\\|^%s$"
110                              (regexp-quote mail-header-separator))
111                      nil t)
112                     (match-beginning 0)
113                   (point-max))))))
114
115 (defun canlock-delete-headers ()
116   "Delete Cancel-Key or Cancel-Lock headers in the narrowed buffer."
117   (let ((case-fold-search t))
118     (goto-char (point-min))
119     (while (re-search-forward "^Cancel-\\(Key\\|Lock\\):" nil t)
120       (delete-region (match-beginning 0)
121                      (if (re-search-forward "^[^\t ]" nil t)
122                          (goto-char (match-beginning 0))
123                        (point-max))))))
124
125 (defun canlock-fetch-fields (&optional key)
126   "Return a list of the values of Cancel-Lock header.
127 If KEY is non-nil, look for a Cancel-Key header instead.  The buffer
128 is expected to be narrowed to just the headers of the message."
129   (let ((field (mail-fetch-field (if key "Cancel-Key" "Cancel-Lock")))
130         fields rest
131         (case-fold-search t))
132     (when field
133       (setq fields (split-string field "[\t\n\r ,]+"))
134       (while fields
135         (when (string-match "^sha1:" (setq field (pop fields)))
136           (push (substring field 5) rest)))
137       (nreverse rest))))
138
139 (defun canlock-fetch-id-for-key ()
140   "Return a Message-ID in Cancel, Supersedes or Replaces header.
141 The buffer is expected to be narrowed to just the headers of the
142 message."
143   (or (let ((cancel (mail-fetch-field "Control")))
144         (and cancel
145              (string-match "^cancel[\t ]+\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
146                            cancel)
147              (match-string 1 cancel)))
148       (mail-fetch-field "Supersedes")
149       (mail-fetch-field "Replaces")))
150
151 ;;;###autoload
152 (defun canlock-insert-header (&optional id-for-key id-for-lock password)
153   "Insert a Cancel-Key and/or a Cancel-Lock header if possible."
154   (let (news control key-for-key key-for-lock)
155     (save-excursion
156       (save-restriction
157         (canlock-narrow-to-header)
158         (when (setq news (or canlock-force-insert-header
159                              (mail-fetch-field "Newsgroups")))
160           (unless id-for-key
161             (setq id-for-key (canlock-fetch-id-for-key)))
162           (if (and (setq control (mail-fetch-field "Control"))
163                    (string-match "^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