4f8348372cbc25521bc3e56ccead7bb1056a1d77
[elisp/gnus.git-] / lisp / mml2015.el
1 ;;; mml2015.el --- MIME Security with Pretty Good Privacy (PGP)
2 ;; Copyright (C) 2000, 2001 Free Software Foundation, Inc.
3
4 ;; Author: Shenghuo Zhu <zsh@cs.rochester.edu>
5 ;; Keywords: PGP MIME MML
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published
11 ;; by the Free Software Foundation; either version 2, or (at your
12 ;; option) any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 ;; General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; 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 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29 (require 'mm-decode)
30
31 (defvar mml2015-use (or
32                      (progn
33                        (ignore-errors
34                          (require 'gpg))
35                        (and (fboundp 'gpg-sign-detached)
36                             'gpg))
37                      (progn (ignore-errors
38                               (load "mc-toplev"))
39                             (and (fboundp 'mc-encrypt-generic)
40                                  (fboundp 'mc-sign-generic)
41                                  (fboundp 'mc-cleanup-recipient-headers)
42                                  'mailcrypt)))
43   "The package used for PGP/MIME.")
44
45 ;; Something is not RFC2015.
46 (defvar mml2015-function-alist
47   '((mailcrypt mml2015-mailcrypt-sign
48                mml2015-mailcrypt-encrypt
49                mml2015-mailcrypt-verify
50                mml2015-mailcrypt-decrypt
51                mml2015-mailcrypt-clear-verify
52                mml2015-mailcrypt-clear-decrypt)
53     (gpg mml2015-gpg-sign
54          mml2015-gpg-encrypt
55          mml2015-gpg-verify
56          mml2015-gpg-decrypt
57          mml2015-gpg-clear-verify
58          mml2015-gpg-clear-decrypt))
59   "Alist of PGP/MIME functions.")
60
61 (defvar mml2015-result-buffer nil)
62
63 ;;; mailcrypt wrapper
64
65 (eval-and-compile
66   (autoload 'mailcrypt-decrypt "mailcrypt")
67   (autoload 'mailcrypt-verify "mailcrypt")
68   (autoload 'mc-pgp-always-sign "mailcrypt")
69   (autoload 'mc-encrypt-generic "mc-toplev")
70   (autoload 'mc-cleanup-recipient-headers "mc-toplev")
71   (autoload 'mc-sign-generic "mc-toplev"))
72
73 (eval-when-compile
74   (defvar mc-default-scheme)
75   (defvar mc-schemes))
76
77 (defvar mml2015-decrypt-function 'mailcrypt-decrypt)
78 (defvar mml2015-verify-function 'mailcrypt-verify)
79
80 (defun mml2015-mailcrypt-decrypt (handle ctl)
81   (catch 'error
82     (let (child handles result)
83       (unless (setq child (mm-find-part-by-type
84                            (cdr handle)
85                            "application/octet-stream" nil t))
86         (mm-set-handle-multipart-parameter
87          mm-security-handle 'gnus-info "Corrupted")
88         (throw 'error handle))
89       (with-temp-buffer
90         (mm-insert-part child)
91         (setq result
92               (condition-case err
93                   (funcall mml2015-decrypt-function)
94                 (error
95                  (mm-set-handle-multipart-parameter
96                   mm-security-handle 'gnus-details (cadr err))
97                  nil)
98                 (quit
99                  (mm-set-handle-multipart-parameter
100                   mm-security-handle 'gnus-details "Quit.")
101                  nil)))
102         (unless (car result)
103           (mm-set-handle-multipart-parameter
104            mm-security-handle 'gnus-info "Failed")
105           (throw 'error handle))
106         (setq handles (mm-dissect-buffer t)))
107       (mm-destroy-parts handle)
108       (mm-set-handle-multipart-parameter
109        mm-security-handle 'gnus-info "OK")
110       (if (listp (car handles))
111           handles
112         (list handles)))))
113
114 (defun mml2015-mailcrypt-clear-decrypt ()
115   (let (result)
116     (setq result
117           (condition-case err
118               (funcall mml2015-decrypt-function)
119             (error
120              (mm-set-handle-multipart-parameter
121               mm-security-handle 'gnus-details (cadr err))
122              nil)
123             (quit
124              (mm-set-handle-multipart-parameter
125               mm-security-handle 'gnus-details "Quit.")
126              nil)))
127     (if (car result)
128         (mm-set-handle-multipart-parameter
129          mm-security-handle 'gnus-info "OK")
130       (mm-set-handle-multipart-parameter
131        mm-security-handle 'gnus-info "Failed"))))
132
133 (defun mml2015-fix-micalg (alg)
134   (and alg
135        (upcase (if (string-match "^pgp-" alg)
136                    (substring alg (match-end 0))
137                  alg))))
138
139 (defun mml2015-mailcrypt-verify (handle ctl)
140   (catch 'error
141     (let (part)
142       (unless (setq part (mm-find-raw-part-by-type
143                           ctl (or (mm-handle-multipart-ctl-parameter
144                                    ctl 'protocol)
145                                   "application/pgp-signature")
146                           t))
147         (mm-set-handle-multipart-parameter
148          mm-security-handle 'gnus-info "Corrupted")
149         (throw 'error handle))
150       (with-temp-buffer
151         (insert "-----BEGIN PGP SIGNED MESSAGE-----\n")
152         (insert (format "Hash: %s\n\n"
153                         (or (mml2015-fix-micalg
154                              (mm-handle-multipart-ctl-parameter
155                               ctl 'micalg))
156                             "SHA1")))
157         (save-restriction
158           (narrow-to-region (point) (point))
159           (insert part "\n")
160           (goto-char (point-min))
161           (while (not (eobp))
162             (if (looking-at "^-")
163                 (insert "- "))
164             (forward-line)))
165         (unless (setq part (mm-find-part-by-type
166                             (cdr handle) "application/pgp-signature" nil t))
167           (mm-set-handle-multipart-parameter
168            mm-security-handle 'gnus-info "Corrupted")
169           (throw 'error handle))
170         (save-restriction
171           (narrow-to-region (point) (point))
172           (mm-insert-part part)
173           (goto-char (point-min))
174           (if (re-search-forward "^-----BEGIN PGP [^-]+-----\r?$" nil t)
175               (replace-match "-----BEGIN PGP SIGNATURE-----" t t))
176           (if (re-search-forward "^-----END PGP [^-]+-----\r?$" nil t)
177               (replace-match "-----END PGP SIGNATURE-----" t t)))
178         (unless (condition-case err
179                     (funcall mml2015-verify-function)
180                   (error
181                    (mm-set-handle-multipart-parameter
182                     mm-security-handle 'gnus-details (cadr err))
183                    nil)
184                   (quit
185                    (mm-set-handle-multipart-parameter
186                     mm-security-handle 'gnus-details "Quit.")
187                    nil))
188           (mm-set-handle-multipart-parameter
189            mm-security-handle 'gnus-info "Failed")
190           (throw 'error handle)))
191       (mm-set-handle-multipart-parameter
192        mm-security-handle 'gnus-info "OK")
193       handle)))
194
195 (defun mml2015-mailcrypt-clear-verify ()
196   (if (condition-case err
197           (funcall mml2015-verify-function)
198         (error
199          (mm-set-handle-multipart-parameter
200           mm-security-handle 'gnus-details (cadr err))
201          nil)
202         (quit
203          (mm-set-handle-multipart-parameter
204           mm-security-handle 'gnus-details "Quit.")
205          nil))
206       (mm-set-handle-multipart-parameter
207        mm-security-handle 'gnus-info "OK")
208     (mm-set-handle-multipart-parameter
209      mm-security-handle 'gnus-info "Failed")))
210
211 (defun mml2015-mailcrypt-sign (cont)
212   (mc-sign-generic (message-options-get 'message-sender)
213                    nil nil nil nil)
214   (let ((boundary
215          (funcall mml-boundary-function (incf mml-multipart-number)))
216         hash point)
217     (goto-char (point-min))
218     (unless (re-search-forward "^-----BEGIN PGP SIGNED MESSAGE-----\r?$" nil t)
219       (error "Cannot find signed begin line." ))
220     (goto-char (match-beginning 0))
221     (forward-line 1)
222     (unless (looking-at "Hash:[ \t]*\\([a-zA-Z0-9]+\\)")
223       (error "Cannot not find PGP hash." ))
224     (setq hash (match-string 1))
225     (unless (re-search-forward "^$" nil t)
226       (error "Cannot not find PGP message." ))
227     (forward-line 1)
228     (delete-region (point-min) (point))
229     (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
230                     boundary))
231     (insert (format "\tmicalg=pgp-%s; protocol=\"application/pgp-signature\"\n"
232                     (downcase hash)))
233     (insert (format "\n--%s\n" boundary))
234     (setq point (point))
235     (goto-char (point-max))
236     (unless (re-search-backward "^-----END PGP SIGNATURE-----\r?$" nil t)
237       (error "Cannot find signature part." ))
238     (replace-match "-----END PGP MESSAGE-----" t t)
239     (goto-char (match-beginning 0))
240     (unless (re-search-backward "^-----BEGIN PGP SIGNATURE-----\r?$"
241                                 nil t)
242       (error "Cannot find signature part." ))
243     (replace-match "-----BEGIN PGP MESSAGE-----" t t)
244     (goto-char (match-beginning 0))
245     (save-restriction
246       (narrow-to-region point (point))
247       (goto-char point)
248       (while (re-search-forward "^- -" nil t)
249         (replace-match "-" t t))
250       (goto-char (point-max)))
251     (insert (format "--%s\n" boundary))
252     (insert "Content-Type: application/pgp-signature\n\n")
253     (goto-char (point-max))
254     (insert (format "--%s--\n" boundary))
255     (goto-char (point-max))))
256
257 (defun mml2015-mailcrypt-encrypt (cont)
258   (let ((mc-pgp-always-sign
259          (or mc-pgp-always-sign
260              (eq t (or (message-options-get 'message-sign-encrypt)
261                        (message-options-set
262                         'message-sign-encrypt
263                         (or (y-or-n-p "Sign the message? ")
264                             'not))))
265              'never)))
266     (mm-with-unibyte-current-buffer-mule4
267       (mc-encrypt-generic
268        (or (message-options-get 'message-recipients)
269            (message-options-set 'message-recipients
270                               (mc-cleanup-recipient-headers
271                                (read-string "Recipients: "))))
272        nil nil nil
273        (message-options-get 'message-sender))))
274   (goto-char (point-min))
275   (unless (looking-at "-----BEGIN PGP MESSAGE-----")
276     (error "Fail to encrypt the message."))
277   (let ((boundary
278          (funcall mml-boundary-function (incf mml-multipart-number))))
279     (insert (format "Content-Type: multipart/encrypted; boundary=\"%s\";\n"
280                     boundary))
281     (insert "\tprotocol=\"application/pgp-encrypted\"\n\n")
282     (insert (format "--%s\n" boundary))
283     (insert "Content-Type: application/pgp-encrypted\n\n")
284     (insert "Version: 1\n\n")
285     (insert (format "--%s\n" boundary))
286     (insert "Content-Type: application/octet-stream\n\n")
287     (goto-char (point-max))
288     (insert (format "--%s--\n" boundary))
289     (goto-char (point-max))))
290
291 ;;; gpg wrapper
292
293 (eval-and-compile
294   (autoload 'gpg-decrypt "gpg")
295   (autoload 'gpg-verify "gpg")
296   (autoload 'gpg-verify-cleartext "gpg")
297   (autoload 'gpg-sign-detached "gpg")
298   (autoload 'gpg-sign-encrypt "gpg")
299   (autoload 'gpg-passphrase-read "gpg"))
300
301 (defun mml2015-gpg-passphrase ()
302   (or (message-options-get 'gpg-passphrase)
303       (message-options-set 'gpg-passphrase (gpg-passphrase-read))))
304
305 (defun mml2015-gpg-decrypt-1 ()
306   (let ((cipher (current-buffer)) plain result)
307     (if (with-temp-buffer
308           (prog1
309               (gpg-decrypt cipher (setq plain (current-buffer))
310                            mml2015-result-buffer nil)
311             (mm-set-handle-multipart-parameter
312              mm-security-handle 'gnus-details
313              (with-current-buffer mml2015-result-buffer
314                (buffer-string)))
315             (set-buffer cipher)
316             (erase-buffer)
317             (insert-buffer plain)))
318         '(t)
319       ;; Some wrong with the return value, check plain text buffer.
320       (if (> (point-max) (point-min))
321           '(t)
322         nil))))
323
324 (defun mml2015-gpg-decrypt (handle ctl)
325   (let ((mml2015-decrypt-function 'mml2015-gpg-decrypt-1))
326     (mml2015-mailcrypt-decrypt handle ctl)))
327
328 (defun mml2015-gpg-clear-decrypt ()
329   (let (result)
330     (setq result (mml2015-gpg-decrypt-1))
331     (if (car result)
332         (mm-set-handle-multipart-parameter
333          mm-security-handle 'gnus-info "OK")
334       (mm-set-handle-multipart-parameter
335        mm-security-handle 'gnus-info "Failed"))))
336
337 (defun mml2015-gpg-extract-from ()
338   (goto-char (point-min))
339   (if (re-search-forward "^gpg: Good signature from \"\\(.*\\)\"$" nil t)
340       (match-string 1)
341     "From unknown user"))
342
343 (defun mml2015-gpg-verify (handle ctl)
344   (catch 'error
345     (let (part message signature)
346       (unless (setq part (mm-find-raw-part-by-type
347                           ctl (or (mm-handle-multipart-ctl-parameter
348                                    ctl 'protocol)
349                                   "application/pgp-signature")
350                           t))
351         (mm-set-handle-multipart-parameter
352          mm-security-handle 'gnus-info "Corrupted")
353         (throw 'error handle))
354       (with-temp-buffer
355         (setq message (current-buffer))
356         (insert part)
357         (with-temp-buffer
358           (setq signature (current-buffer))
359           (unless (setq part (mm-find-part-by-type
360                               (cdr handle) "application/pgp-signature" nil t))
361             (mm-set-handle-multipart-parameter
362              mm-security-handle 'gnus-info "Corrupted")
363             (throw 'error handle))
364           (mm-insert-part part)
365           (unless (condition-case err
366                       (prog1
367                           (gpg-verify message signature mml2015-result-buffer)
368                         (mm-set-handle-multipart-parameter
369                          mm-security-handle 'gnus-details
370                          (with-current-buffer mml2015-result-buffer
371                            (buffer-string))))
372                     (error
373                      (mm-set-handle-multipart-parameter
374                       mm-security-handle 'gnus-details (cadr err))
375                      nil)
376                     (quit
377                      (mm-set-handle-multipart-parameter
378                       mm-security-handle 'gnus-details "Quit.")
379                      nil))
380             (mm-set-handle-multipart-parameter
381              mm-security-handle 'gnus-info "Failed")
382             (throw 'error handle)))
383         (mm-set-handle-multipart-parameter
384          mm-security-handle 'gnus-info 
385          (with-current-buffer mml2015-result-buffer 
386            (mml2015-gpg-extract-from))))
387       handle)))
388
389 (defun mml2015-gpg-clear-verify ()
390   (if (condition-case err
391           (prog1
392               (gpg-verify-cleartext (current-buffer) mml2015-result-buffer)
393             (mm-set-handle-multipart-parameter
394              mm-security-handle 'gnus-details
395              (with-current-buffer mml2015-result-buffer
396                (buffer-string))))
397         (error
398          (mm-set-handle-multipart-parameter
399           mm-security-handle 'gnus-details (cadr err))
400          nil)
401         (quit
402          (mm-set-handle-multipart-parameter
403           mm-security-handle 'gnus-details "Quit.")
404          nil))
405       (mm-set-handle-multipart-parameter
406        mm-security-handle 'gnus-info 
407        (with-current-buffer mml2015-result-buffer 
408          (mml2015-gpg-extract-from)))
409     (mm-set-handle-multipart-parameter
410      mm-security-handle 'gnus-info "Failed")))
411
412 (defun mml2015-gpg-sign (cont)
413   (let ((boundary
414          (funcall mml-boundary-function (incf mml-multipart-number)))
415         (text (current-buffer)) signature)
416     (goto-char (point-max))
417     (unless (bolp)
418       (insert "\n"))
419     (with-temp-buffer
420       (unless (gpg-sign-detached text (setq signature (current-buffer))
421                                  mml2015-result-buffer
422                                  nil
423                                  (message-options-get 'message-sender)
424                                  t t) ; armor & textmode
425         (unless (> (point-max) (point-min))
426           (pop-to-buffer mml2015-result-buffer)
427           (error "Sign error.")))
428       (goto-char (point-min))
429       (while (re-search-forward "\r+$" nil t)
430         (replace-match "" t t))
431       (set-buffer text)
432       (goto-char (point-min))
433       (insert (format "Content-Type: multipart/signed; boundary=\"%s\";\n"
434                       boundary))
435       ;;; FIXME: what is the micalg?
436       (insert "\tmicalg=pgp-sha1; protocol=\"application/pgp-signature\"\n")
437       (insert (format "\n--%s\n" boundary))
438       (goto-char (point-max))
439       (insert (format "\n--%s\n" boundary))
440       (insert "Content-Type: application/pgp-signature\n\n")
441       (insert-buffer signature)
442       (goto-char (point-max))
443       (insert (format "--%s--\n" boundary))
444       (goto-char (point-max)))))
445
446 (defun mml2015-gpg-encrypt (cont)
447   (let ((boundary
448          (funcall mml-boundary-function (incf mml-multipart-number)))
449         (text (current-buffer))
450         cipher)
451     (mm-with-unibyte-current-buffer-mule4
452       (with-temp-buffer
453         (unless (gpg-sign-encrypt
454                  text (setq cipher (current-buffer))
455                  mml2015-result-buffer
456                  (split-string
457                   (or
458                    (message-options-get 'message-recipients)
459                    (message-options-set 'message-recipients
460                                         (read-string "Recipients: ")))
461                   "[ \f\t\n\r\v,]+")
462                  nil
463                  (message-options-get 'message-sender)
464                  t t) ; armor & textmode
465           (unless (> (point-max) (point-min))
466             (pop-to-buffer mml2015-result-buffer)
467             (error "Encrypt error.")))
468         (goto-char (point-min))
469         (while (re-search-forward "\r+$" nil t)
470           (replace-match "" t t))
471         (set-buffer text)
472         (delete-region (point-min) (point-max))
473         (insert (format "Content-Type: multipart/encrypted; boundary=\"%s\";\n"
474                         boundary))
475         (insert "\tprotocol=\"application/pgp-encrypted\"\n\n")
476         (insert (format "--%s\n" boundary))
477         (insert "Content-Type: application/pgp-encrypted\n\n")
478         (insert "Version: 1\n\n")
479         (insert (format "--%s\n" boundary))
480         (insert "Content-Type: application/octet-stream\n\n")
481         (insert-buffer cipher)
482         (goto-char (point-max))
483         (insert (format "--%s--\n" boundary))
484         (goto-char (point-max))))))
485
486 ;;; General wrapper
487
488 (defun mml2015-clean-buffer ()
489   (if (gnus-buffer-live-p mml2015-result-buffer)
490       (with-current-buffer mml2015-result-buffer
491         (erase-buffer)
492         t)
493     (setq mml2015-result-buffer
494           (gnus-get-buffer-create "*MML2015 Result*"))
495     nil))
496
497 (defsubst mml2015-clear-decrypt-function ()
498   (nth 6 (assq mml2015-use mml2015-function-alist)))
499
500 (defsubst mml2015-clear-verify-function ()
501   (nth 5 (assq mml2015-use mml2015-function-alist)))
502
503 ;;;###autoload
504 (defun mml2015-decrypt (handle ctl)
505   (mml2015-clean-buffer)
506   (let ((func (nth 4 (assq mml2015-use mml2015-function-alist))))
507     (if func
508         (funcall func handle ctl)
509       handle)))
510
511 ;;;###autoload
512 (defun mml2015-decrypt-test (handle ctl)
513   mml2015-use)
514
515 ;;;###autoload
516 (defun mml2015-verify (handle ctl)
517   (mml2015-clean-buffer)
518   (let ((func (nth 3 (assq mml2015-use mml2015-function-alist))))
519     (if func
520         (funcall func handle ctl)
521       handle)))
522
523 ;;;###autoload
524 (defun mml2015-verify-test (handle ctl)
525   mml2015-use)
526
527 ;;;###autoload
528 (defun mml2015-encrypt (cont)
529   (mml2015-clean-buffer)
530   (let ((func (nth 2 (assq mml2015-use mml2015-function-alist))))
531     (if func
532         (funcall func cont)
533       (error "Cannot find encrypt function."))))
534
535 ;;;###autoload
536 (defun mml2015-sign (cont)
537   (mml2015-clean-buffer)
538   (let ((func (nth 1 (assq mml2015-use mml2015-function-alist))))
539     (if func
540         (funcall func cont)
541       (error "Cannot find sign function."))))
542
543 ;;;###autoload
544 (defun mml2015-self-encrypt ()
545   (mml2015-encrypt nil))
546
547 (provide 'mml2015)
548
549 ;;; mml2015.el ends here