1 ;;; mime-mc.el --- Mailcrypt interface for SEMI
3 ;; Copyright (C) 1996,1997,1998,1999 MORIOKA Tomohiko
5 ;; Author: MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;; Katsumi Yamaoka <yamaoka@jpl.org>
7 ;; Keywords: PGP, GnuPG, security, MIME, multimedia, mail, news
9 ;; This file is part of SEMI (Secure Emacs MIME Interface).
11 ;; This program is free software; you can redistribute it and/or
12 ;; modify it under the terms of the GNU General Public License as
13 ;; published by the Free Software Foundation; either version 2, or (at
14 ;; your option) any later version.
16 ;; This program is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ;; General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
33 (function (lambda (elem) (apply 'autoload elem)))
35 (mc-gpg-debug-print "mc-gpg")
36 (mc-gpg-encrypt-region "mc-gpg")
37 (mc-gpg-lookup-key "mc-gpg")
38 (mc-pgp50-encrypt-region "mc-pgp5")
39 (mc-pgp50-lookup-key "mc-pgp5")
40 (mc-pgp-encrypt-region "mc-pgp")
41 (mc-pgp-lookup-key "mc-pgp")
42 (mc-snarf-keys "mc-toplev")
45 (defvar mc-gpg-comment)
46 (defvar mc-gpg-extra-args)
48 (defvar mc-gpg-user-id)
49 (defvar mc-pgp50-comment)
50 (defvar mc-pgp50-pgps-path)
51 (defvar mc-pgp50-user-id)
52 (defvar mc-pgp-comment)
54 (defvar mc-pgp-user-id)
57 ;;; @ Generic functions
60 (defun mime-mc-setversion (&optional version)
61 "Select `pgp-version' and `mc-default-scheme' if possible.
62 VERSION should be a string or a symbol."
64 (let ((oldversion pgp-version)
65 (table '(("GnuPG" . gpg) ("PGP 5.0i" . pgp50) ("PGP 2.6" . pgp)
66 ("gnupg" . gpg) ("gpg" . gpg) ("pgp5" . pgp50)
67 ("pgp50" . pgp50) ("pgp2" . pgp) ("pgp" . pgp)
68 ("5.0" . pgp50) ("2.6" . pgp))))
70 (setq version (completing-read
71 (format "Select PGP version (currently %s): "
72 (car (rassoc oldversion table)))
74 pgp-version (or (cdr (assoc version table))
77 (setq pgp-version (or (cdr (assoc version table)) oldversion))
78 (if (memq version '(gpg pgp50 pgp))
79 (setq pgp-version version)
83 (cdr (assq pgp-version
84 '((gpg . "gpg") (pgp50 . "5.0") (pgp . "2.6"))))
87 (message "PGP version set to %s." (car (rassq pgp-version table)))
90 (defun mime-mc-insert-public-key (&optional userid scheme)
93 (or scheme (intern (format "mc-scheme-%s" pgp-version)))
96 (defun mime-mc-verify ()
97 (let ((mc-default-scheme (intern (format "mc-scheme-%s" pgp-version))))
101 (defun mime-mc-decrypt ()
102 (let ((mc-default-scheme (intern (format "mc-scheme-%s" pgp-version))))
106 (defun mime-mc-snarf-keys ()
107 (let ((mc-default-scheme (intern (format "mc-scheme-%s" pgp-version))))
112 ;;; @ GnuPG functions
115 (defun mime-mc-gpg-process-region
116 (beg end passwd program args parser bufferdummy boundary)
117 (let ((obuf (current-buffer))
118 (process-connection-type nil)
119 (shell-file-name "/bin/sh") ;; ??? force? need sh (not tcsh) for "2>"
122 stderr-tempfilename stderr-buf
123 status-tempfilename status-buf
124 proc rc status parser-result
126 (mc-gpg-debug-print (format
127 "(mc-gpg-process-region beg=%s end=%s passwd=%s program=%s args=%s parser=%s bufferdummy=%s)"
128 beg end passwd program args parser bufferdummy))
129 (setq stderr-tempfilename
130 (make-temp-name (expand-file-name "mailcrypt-gpg-stderr-"
132 (setq status-tempfilename
133 (make-temp-name (expand-file-name "mailcrypt-gpg-status-"
137 ;; get output places ready
138 (setq mybuf (get-buffer-create " *mailcrypt stdout temp"))
142 (buffer-disable-undo mybuf)
145 (setq args (append '("--passphrase-fd" "0") args)))
146 (setq args (append (list (concat "2>" stderr-tempfilename)) args))
147 (setq args (append (list (concat "3>" status-tempfilename)) args))
148 (setq args (append '("--status-fd" "3") args))
150 (if mc-gpg-extra-args
151 (setq args (append mc-gpg-extra-args args)))
153 (mc-gpg-debug-print (format "prog is %s, args are %s"
155 (mapconcat '(lambda (x)
160 (apply 'start-process-shell-command "*GPG*" mybuf
162 ;; send in passwd if necessary
165 (process-send-string proc (concat passwd "\n"))
166 (or mc-passwd-timeout (mc-deactivate-passwd t))))
167 ;; send in the region
168 (process-send-region proc beg end)
170 (process-send-eof proc)
171 ;; wait for it to finish
172 (while (eq 'run (process-status proc))
173 (accept-process-output proc 5))
174 ;; remember result codes
175 (setq status (process-status proc))
176 (setq rc (process-exit-status proc))
177 (mc-gpg-debug-print (format "prog finished, rc=%s" rc))
179 ;; Hack to force a status_notify() in Emacs 19.29
180 (delete-process proc)
182 ;; remove the annoying "yes your process has finished" message
184 (goto-char (point-max))
185 (if (re-search-backward "\nProcess \\*GPG.*\n\\'" nil t)
186 (delete-region (match-beginning 0) (match-end 0)))
187 (goto-char (point-min))
189 (while (search-forward "\r\n" nil t)
190 (replace-match "\n"))
192 ;; ponder process death: signal, not just rc!=0
193 (if (or (eq 'stop status) (eq 'signal status))
195 (error "%s exited abnormally: '%s'" program rc) ;;is rc a string?
199 (error "%s could not be found" program) ;; at least on my system
203 (setq stderr-buf (get-buffer-create " *mailcrypt stderr temp"))
204 (buffer-disable-undo stderr-buf)
205 (set-buffer stderr-buf)
207 (insert-file-contents stderr-tempfilename)
210 (setq status-buf (get-buffer-create " *mailcrypt status temp"))
211 (buffer-disable-undo status-buf)
212 (set-buffer status-buf)
214 (insert-file-contents status-tempfilename)
218 (setq parser-result (funcall parser mybuf stderr-buf status-buf rc))
219 (mc-gpg-debug-print (format " parser returned %s" parser-result))
221 ;; what did the parser tell us?
222 (if (car parser-result)
223 ;; yes, replace region
228 (narrow-to-region beg end)
230 (insert (format "--%s\n" boundary))
231 (goto-char (point-max))
232 (insert (format "\n--%s
233 Content-Type: application/pgp-signature
234 Content-Transfer-Encoding: 7bit
237 (insert-buffer-substring mybuf)
238 (goto-char (point-max))
239 (insert (format "\n--%s--\n" boundary))
241 (delete-region beg end)
243 (insert-buffer-substring mybuf)
250 (if (and proc (eq 'run (process-status proc)))
251 ;; it is still running. kill it.
252 (interrupt-process proc))
254 (delete-file stderr-tempfilename)
255 (delete-file status-tempfilename)
256 ;; kill off temporary buffers (which would be useful for debugging)
257 (if t ;; nil for easier debugging
259 (if (get-buffer " *mailcrypt stdout temp")
260 (kill-buffer " *mailcrypt stdout temp"))
261 (if (get-buffer " *mailcrypt stderr temp")
262 (kill-buffer " *mailcrypt stderr temp"))
263 (if (get-buffer " *mailcrypt status temp")
264 (kill-buffer " *mailcrypt status temp"))
268 (defun mime-mc-gpg-sign-region (start end &optional id unclear boundary)
269 (if (not (fboundp 'mc-gpg-insert-parser))
272 (let ((buffer (get-buffer-create mc-buffer-name))
274 (parser (function mc-gpg-insert-parser))
275 (pgp-path mc-gpg-path)
277 (setq key (mc-gpg-lookup-key (or id mc-gpg-user-id)))
281 (format "GnuPG passphrase for %s (%s): " (car key) (cdr key))))
289 (list "--passphrase-fd" "0"
290 "--armor" "--batch" "--textmode" "--verbose"
291 "--local-user" (cdr key))))
293 (setq args (nconc args
295 (format "\"%s\"" mc-gpg-comment))))
298 (string-match "^pgp-" boundary))
300 (concat "gpg-" (substring boundary (match-end 0))))
302 (message "Signing as %s ..." (car key))
303 (if (mime-mc-gpg-process-region
304 start end passwd pgp-path args parser buffer boundary)
308 (goto-char (point-min))
311 --[[multipart/signed; protocol=\"application/pgp-signature\";
312 boundary=\"%s\"; micalg=pgp-sha1][7bit]]\n" boundary))
314 (message "Signing as %s ... Done." (car key))
318 (defun mime-mc-gpg-encrypt-region (recipients start end &optional id sign)
319 (if (not (fboundp 'mc-gpg-encrypt-region))
322 (let ((mc-pgp-always-sign (if (eq sign 'maybe)
325 (mc-gpg-encrypt-region
326 (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" recipients)
331 ;;; @ PGP 5.0i functions
334 (defun mime-mc-pgp50-process-region
335 (beg end passwd program args parser &optional buffer boundary)
336 (let ((obuf (current-buffer))
337 (process-connection-type nil)
338 (shell-file-name "/bin/sh")
339 mybuf result rgn proc results)
342 (setq mybuf (or buffer (generate-new-buffer " *mailcrypt temp")))
346 (buffer-disable-undo mybuf)
348 (apply 'start-process-shell-command "*PGP*" mybuf program
351 ;; Now hand the process to the parser, which returns the exit
352 ;; status of the dead process and the limits of the region
353 ;; containing the PGP results.
354 (setq results (funcall parser proc obuf beg end mybuf passwd))
355 (setq result (car results))
356 (setq rgn (cadr results))
358 ;; Hack to force a status_notify() in Emacs 19.29
361 ;; Hurm. FIXME; must get better result codes.
365 ;; If the parser found something, migrate it to the old
366 ;; buffer. In particular, the parser's job is to return
367 ;; a cons of the form ( beg . end ) delimited the result
368 ;; of PGP in the new buffer.
374 (narrow-to-region beg end)
376 (insert (format "--%s\n" boundary))
377 (goto-char (point-max))
378 (insert (format "\n--%s
379 Content-Type: application/pgp-signature
380 Content-Transfer-Encoding: 7bit
383 (insert-buffer-substring mybuf (car rgn) (cdr rgn))
384 (goto-char (point-max))
385 (insert (format "\n--%s--\n" boundary))
387 (delete-region beg end)
389 (insert-buffer-substring mybuf (car rgn) (cdr rgn))
392 (delete-region (car rgn) (cdr rgn))))
394 ;; Return nil on failure and exit code on success
397 ;; Cleanup even on nonlocal exit
398 (if (and proc (eq 'run (process-status proc)))
399 (interrupt-process proc))
401 (or buffer (null mybuf) (kill-buffer mybuf))
404 (defun mime-mc-pgp50-sign-parser (proc oldbuf start end newbuf passwd)
405 ;; This function is a copy of `mc-pgp50-sign-parser', however it is
406 ;; modified for parsing a detached sign.
407 (let (result results rgn)
408 ;; (setenv "PGPPASSFD" "0")
410 (goto-char (point-max))
414 (message "Sending passphrase...")
415 (expect-send (concat passwd "\n"))
416 (or mc-passwd-timeout (mc-deactivate-passwd t))
417 (expect "No files specified. Using stdin."
418 (message "Passphrase sent. Signing...")
420 (process-send-region proc start end)
422 (process-send-eof proc)
424 ;; Test output of the program, looking for
428 ;; OPTION 1: Great! The data is now signed!
429 ("-----END PGP SIGNATURE-----"
431 ;; Catch the exit status.
432 (setq result (process-exit-status proc))
433 (delete-process proc)
434 (message "Signing complete.")
436 ;; Delete everything preceding the signed data.
437 (goto-char (point-max))
439 ;; "-----BEGIN PGP SIGNED MESSAGE-----" nil t)
440 "-----BEGIN PGP SIGNATURE-----" nil t)
441 (delete-region (point-min) (match-beginning 0))
442 (setq rgn (point-min))
444 ;; Convert out CR/NL -> NL
445 (goto-char (point-min))
446 (while (search-forward "\r\n" nil t)
447 (replace-match "\n"))
449 ;; Delete everything after the signature.
450 (goto-char (point-min))
452 "-----END PGP SIGNATURE-----\n" nil t)
453 (delete-region (match-end 0) (point-max))
455 ;; Return the exit status, with the region
457 (setq rgn (cons rgn (point-max)))
458 (setq results (list result rgn)))
461 ;; OPTION 1.a: The data is now signed, but is 8bit data.
462 ("-----END PGP MESSAGE-----"
464 ;; Catch the exit status.
465 (setq result (process-exit-status proc))
466 (delete-process proc)
467 (message "Signing complete.")
469 ;; Delete everything preceding the signed data.
470 (goto-char (point-max))
472 "-----BEGIN PGP MESSAGE-----" nil t)
473 (delete-region (point-min) (match-beginning 0))
474 (setq rgn (point-min))
476 ;; Convert out CR/NL -> NL
477 (goto-char (point-min))
478 (while (search-forward "\r\n" nil t)
479 (replace-match "\n"))
481 ;; Delete everything after the signature.
482 (goto-char (point-min))
484 "-----END PGP MESSAGE-----\n" nil t)
485 (delete-region (match-end 0) (point-max))
487 ;; Return the exit status, with the region
489 (setq rgn (cons rgn (point-max)))
490 (setq results (list result rgn)))
493 ;; OPTION 2: Awww...bad passphrase!
494 ("Enter pass phrase:"
495 (mc-deactivate-passwd t)
496 (interrupt-process proc)
497 (delete-process proc)
499 ;; Return the bad news.
500 (setq results '("Incorrect passphrase" nil)))
502 ;; OPTION 3: The program exits.
505 (process-exit-status proc) nil)))))))
508 (defun mime-mc-pgp50-sign-region (start end &optional id unclear boundary)
509 (if (not (fboundp 'mc-pgp50-sign-parser))
512 (let ((process-environment process-environment)
513 (buffer (get-buffer-create mc-buffer-name))
516 (function mime-mc-pgp50-sign-parser)
517 (function mc-pgp50-sign-parser)))
518 (pgp-path mc-pgp50-pgps-path)
520 (setq key (mc-pgp50-lookup-key (or id mc-pgp50-user-id)))
524 (format "PGP passphrase for %s (%s): " (car key) (cdr key))))
525 (setenv "PGPPASSFD" "0")
531 (list "+verbose=1" "+language=us"
532 (format "+clearsig=%s" (if unclear "off" "on"))
533 "+batchmode" "-u" (cdr key))))
535 (setq args (cons (format "+comment=\"%s\"" mc-pgp50-comment) args))
537 (message "Signing as %s ..." (car key))
538 (if (mime-mc-pgp50-process-region
539 start end passwd pgp-path args parser buffer boundary)
543 (goto-char (point-min))
546 --[[multipart/signed; protocol=\"application/pgp-signature\";
547 boundary=\"%s\"; micalg=pgp-sha1][7bit]]\n" boundary))
549 (message "Signing as %s ... Done." (car key))
553 (defun mime-mc-pgp50-encrypt-region (recipients start end &optional id sign)
554 (if (not (fboundp 'mc-pgp50-encrypt-region))
557 (let ((mc-pgp-always-sign (if (eq sign 'maybe)
560 (mc-pgp50-encrypt-region
561 (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" recipients)
566 ;;; @ PGP 2.6 functions
569 (defun mime-mc-process-region
570 (beg end passwd program args parser &optional buffer boundary)
571 (let ((obuf (current-buffer))
572 (process-connection-type nil)
573 mybuf result rgn proc)
576 (setq mybuf (or buffer (generate-new-buffer " *mailcrypt temp")))
580 (buffer-disable-undo mybuf)
582 (apply 'start-process "*PGP*" mybuf program args))
585 (process-send-string proc (concat passwd "\n"))
586 (or mc-passwd-timeout (mc-deactivate-passwd t))))
587 (process-send-region proc beg end)
588 (process-send-eof proc)
589 (while (eq 'run (process-status proc))
590 (accept-process-output proc 5))
591 (setq result (process-exit-status proc))
592 ;; Hack to force a status_notify() in Emacs 19.29
593 (delete-process proc)
595 (goto-char (point-max))
596 (if (re-search-backward "\nProcess \\*PGP.*\n\\'" nil t)
597 (delete-region (match-beginning 0) (match-end 0)))
598 (goto-char (point-min))
600 (while (search-forward "\r\n" nil t)
601 (replace-match "\n"))
602 ;; Hurm. FIXME; must get better result codes.
604 (error "%s exited abnormally: '%s'" program result)
605 (setq rgn (funcall parser result))
606 ;; If the parser found something, migrate it
612 (narrow-to-region beg end)
614 (insert (format "--%s\n" boundary))
615 (goto-char (point-max))
616 (insert (format "\n--%s
617 Content-Type: application/pgp-signature
618 Content-Transfer-Encoding: 7bit
621 (insert-buffer-substring mybuf (car rgn) (cdr rgn))
622 (goto-char (point-max))
623 (insert (format "\n--%s--\n" boundary))
625 (delete-region beg end)
627 (insert-buffer-substring mybuf (car rgn) (cdr rgn))
630 (delete-region (car rgn) (cdr rgn)))))
631 ;; Return nil on failure and exit code on success
633 ;; Cleanup even on nonlocal exit
634 (if (and proc (eq 'run (process-status proc)))
635 (interrupt-process proc))
637 (or buffer (null mybuf) (kill-buffer mybuf)))))
639 (defun mime-mc-pgp-sign-region (start end &optional id unclear boundary)
640 (if (not (fboundp 'mc-pgp-generic-parser))
643 (let ((process-environment process-environment)
644 (buffer (get-buffer-create mc-buffer-name))
646 (parser (function mc-pgp-generic-parser))
647 (pgp-path mc-pgp-path)
649 (setq key (mc-pgp-lookup-key (or id mc-pgp-user-id)))
653 (format "PGP passphrase for %s (%s): " (car key) (cdr key))))
654 (setenv "PGPPASSFD" "0")
660 (list "+verbose=1" "+language=en"
661 (format "+clearsig=%s" (if unclear "off" "on"))
662 "+batchmode" "-u" (cdr key))))
664 (setq args (cons (format "+comment=%s" mc-pgp-comment) args))
666 (message "Signing as %s ..." (car key))
667 (if (mime-mc-process-region
668 start end passwd pgp-path args parser buffer boundary)
672 (goto-char (point-min))
675 --[[multipart/signed; protocol=\"application/pgp-signature\";
676 boundary=\"%s\"; micalg=pgp-md5][7bit]]\n" boundary))
678 (message "Signing as %s ... Done." (car key))
682 (defun mime-mc-pgp-encrypt-region (recipients start end &optional id sign)
683 (if (not (fboundp 'mc-pgp-encrypt-region))
686 (let ((mc-pgp-always-sign (if (eq sign 'maybe)
689 (mc-pgp-encrypt-region
690 (mc-split "\\([ \t\n]*,[ \t\n]*\\)+" recipients)
700 ;;; mime-mc.el ends here