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