| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 |
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 |
|
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 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 |
|---|
| 69 |
buffer 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. |
|---|
| 126 |
If KEY is non-nil, look for a Cancel-Key header instead. The buffer |
|---|
| 127 |
is 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. |
|---|
| 140 |
The buffer is expected to be narrowed to just the headers of the |
|---|
| 141 |
message." |
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 194 |
(defun canlock-verify (&optional buffer) |
|---|
| 195 |
"Verify Cancel-Lock or Cancel-Key in BUFFER. |
|---|
| 196 |
If BUFFER is nil, the current buffer is assumed. Signal an error if |
|---|
| 197 |
it 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 |
|
|---|
| 251 |
|
|---|
| 252 |
|
|---|