diff lisp/mailcrypt/mc-remail.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/mailcrypt/mc-remail.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,862 @@
+;; mc-remail.el --- Remailer support for Mailcrypt
+
+;; Copyright (C) 1995 Patrick LoPresti <patl@lcs.mit.edu>
+
+;;{{{ Licensing
+
+;; This file is intended to be used with GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;}}}
+;;{{{ Load required packages
+
+(require 'mail-utils)
+(require 'sendmail)
+(require 'mailcrypt)
+
+(eval-and-compile
+  (if (not mc-xemacs-p)
+      (progn
+	(autoload 'mc-cleanup-recipient-headers "mc-toplev")
+	(autoload 'mc-encrypt-message "mc-toplev"))))
+
+(eval-and-compile
+  (condition-case nil (require 'mailalias) (error nil)))
+
+;;}}}
+;;{{{ Functions dealing with remailer structures
+
+(defsubst mc-remailer-create (addr id props pre-encr post-encr)
+  "Create a remailer structure.
+
+ADDR is the remailer's Email address, a string.
+
+ID is the remailer's public key ID (a string) or nil if the same as
+ADDR.
+
+PROPS is a list of properties, as strings.
+
+PRE-ENCR is a list of pre-encryption functions.  Its elements will be
+called with the remailer structure itself as argument.
+
+POST-ENCR is similar, but for post-encryption functions."
+(list 'remailer addr id props pre-encr post-encr))
+
+(defsubst mc-remailerp (remailer)
+  "Test whether REMAILER is a valid remailer struct."
+  (and (listp remailer) (eq 'remailer (car-safe remailer))))
+
+(defsubst mc-remailer-address (remailer)
+  "Return the Email address of REMAILER."
+  (nth 1 remailer))
+
+(defsubst mc-remailer-userid (remailer)
+  "Return the userid with which to look up the public key for REMAILER."
+  (or (nth 2 remailer)
+      (mc-strip-address (mc-remailer-address remailer))))
+
+(defsubst mc-remailer-properties (remailer)
+  "Return the property list for REMAILER"
+  (nth 3 remailer))
+
+(defsubst mc-remailer-pre-encrypt-hooks (remailer)
+  "Return the list of pre-encryption hooks for REMAILER."
+  (nth 4 remailer))
+
+(defsubst mc-remailer-post-encrypt-hooks (remailer)
+  "Return the list of post-encryption hooks for REMAILER."
+  (nth 5 remailer))
+
+(defun mc-remailer-remove-property (remailer prop)
+  (let ((props (append (mc-remailer-properties remailer) nil)))
+    (setq props (delete prop props))
+    (mc-remailer-create
+     (mc-remailer-address remailer)
+     (mc-remailer-userid remailer)
+     props
+     (mc-remailer-pre-encrypt-hooks remailer)
+     (mc-remailer-post-encrypt-hooks remailer))))
+
+;;}}}
+;;{{{ User variables
+
+(defvar mc-response-block-included-headers
+  '("From" "To" "Newsgroups")
+  "List of header fields to include in response blocks.
+
+These will be copied into the deepest layer of the response block to
+help you identify it when it is used to Email you.")
+
+
+(defvar mc-remailer-tag "(*REMAILER*)"
+  "A string which marks an Email address as belonging to a remailer.")
+
+(defvar mc-levien-file-name "~/.remailers"
+  "The file containing a Levien format list of remailers.
+
+The file is read by `mc-read-levien-file' and `mc-reread-levien-file'.
+
+The file should include lines of the following form (other lines
+are ignored):
+
+$remailer{\"NAME\"} = \"<EMAIL ADDRESS> PROPERTIES\";
+
+PROPERTIES is a space-separated set of strings.
+
+This format is named after Raphael Levien, who maintains a list of
+active remailers.  Do \"finger remailer-list@kiwi.cs.berkeley.edu\"
+for the latest copy of his list.")
+
+(defvar mc-remailer-user-chains nil
+  "An alist of remailer chains defined by the user.
+
+Format is
+
+((NAME . REMAILER-LIST)
+ (NAME . REMAILER-LIST)
+ ...)
+
+NAME must be a string.
+
+REMAILER-LIST may be an arbitrary sequence, not just a list.  Its
+elements may be any of the following:
+
+1) A remailer structure created by `mc-remailer-create'.  This is
+   the base case.
+
+2) A string naming another remailer chain to be spliced in
+   at this point.
+
+3) A positive integer N representing a chain to be spliced in at this
+   point and consisting of a random permutation of the top N remailers
+   as ordered in the file `mc-levien-file-name'.
+
+4) An arbitrary Lisp form to be evaluated, which should
+   return another REMAILER-LIST to be recursively processed and
+   spliced in at this point.
+
+The complete alist of chains is given by the union of the two lists
+`mc-remailer-internal-chains' and `mc-remailer-user-chains'.")
+
+(defvar mc-remailer-internal-chains nil
+  "List of \"internal\" remailer chains.
+
+This variable is normally generated automatically from a human-readable
+list of remailers; see, for example, the function `mc-reread-levien-file'.
+
+To define your own chains, you probably want to use the variable
+`mc-remailer-user-chains'.  See that variable's documentation for
+format information.")
+
+(defvar mc-remailer-internal-ranking nil
+  "Ordered list of remailers, most reliable first.
+
+This variable is normally generated automatically from a human-readable
+list of remailers; see, for example, the function `mc-reread-levien-file'.")
+
+(defvar mc-remailer-user-response-block
+  (function
+   (lambda (addr lines block)
+     (concat
+      ";;;\n"
+      (format
+       "To reply to this message, take the following %d-line block, remove\n"
+       lines)
+      "leading \"- \" constructs (if any), and place it at the top of a\n"
+      (format "message to %s :\n" addr)
+      block)))
+  "A function called to generate response block text.
+
+Value should be a function taking three arguments (ADDR LINES BLOCK).
+ADDR is the address to which the response should be sent.
+LINES is the number of lines in the encrypted response block.
+BLOCK is the response block itself.
+Function should return a string to be inserted into the buffer
+by mc-remailer-insert-response-block.")
+
+(defvar mc-remailer-pseudonyms nil
+  "*A list of your pseudonyms.
+
+This is a list of strings.  Completion against it will be available
+when you are prompted for your pseudonym.")
+
+(defvar mc-remailer-preserved-headers
+  '("References" "Followup-to" "In-reply-to")
+  "*Header fields which are preserved as hashmark headers when rewriting.
+
+This is a list of strings naming the preserved headers.  Note that
+\"Subject\", \"Newsgroups\", and \"To\" are handled specially and
+should not be included in this list.")
+
+;;}}}
+;;{{{ Handling Levien format remailer lists
+
+(defun mc-parse-levien-buffer ()
+  ;; Parse a buffer in Levien format.
+  (goto-char (point-min))
+  (let (chains remailer remailer-name ranking)
+    (while
+	(re-search-forward
+	 "^\\$remailer{\"\\(.+\\)\"}[ \t]*=[ \t]*\"\\(.*\\)\";"
+	 nil t)
+      (let ((name (buffer-substring-no-properties
+		   (match-beginning 1) (match-end 1)))
+	    property-list address
+	    (value-start (match-beginning 2))
+	    (value-end (match-end 2)))
+	(goto-char value-start)
+	(while (re-search-forward "[^ \t]+" value-end 'no-error)
+	  (setq property-list
+		(append
+		 property-list
+		 (list (buffer-substring-no-properties
+			(match-beginning 0) (match-end 0))))))
+	(setq address (car property-list)
+	      property-list (cdr property-list)
+	      remailer-name name)
+	(if (not
+	     (or (member "mix" property-list)
+		 (and (or (member "pgp" property-list)
+			  (member "pgp." property-list))
+		      (or (member "cpunk" property-list)
+			  (member "eric" property-list)))))
+	    (setq remailer nil)
+	  (setq remailer
+		(mc-remailer-create
+		 address		; Address
+		 (if (member "pgp." property-list)
+		     name)		; User ID
+		 property-list
+		 '(mc-generic-pre-encrypt-function) ; Pre-encrypt hooks
+		 '(mc-generic-post-encrypt-function) ; Post-encrypt hooks
+		 ))))
+      (if (not (null remailer))
+	  (setq chains (cons (list remailer-name remailer) chains))))
+    (goto-char (point-min))
+    (if (re-search-forward "----------" nil t)
+	(while (re-search-forward "^\\([a-zA-Z0-9\\-]+\\) " nil t)
+	  (setq remailer-name (buffer-substring-no-properties
+			       (match-beginning 1) (match-end 1)))
+	  (if (assoc remailer-name chains)
+	      (setq ranking (append ranking (list remailer-name))))))
+    (cons chains ranking)))
+
+(defun mc-read-levien-file ()
+  "Read the Levien format file specified in `mc-levien-file-name'.
+Return an alist of length-1 chains, one for each remailer, named
+after the remailer.  Only include remailers supporting PGP
+encryption."
+  (save-excursion
+    (if (file-readable-p mc-levien-file-name)
+	(prog2
+	    (find-file-read-only mc-levien-file-name)
+	    (mc-parse-levien-buffer)
+	  (bury-buffer)))))
+
+(defun mc-reread-levien-file ()
+  "Read the Levien format file specified in `mc-levien-file-name'.
+
+Place result in `mc-remailer-internal-chains' and `mc-remailer-internal-ranking'.
+
+See the documentation for the variable `mc-levien-file-name' for
+a description of Levien file format."
+  (interactive)
+  (let ((parsed-levien-file (mc-read-levien-file)))
+    (setq mc-remailer-internal-chains (car parsed-levien-file)
+	  mc-remailer-internal-ranking (cdr parsed-levien-file))))
+
+;;}}}
+;;{{{ Arbitrary chain choice
+
+(defun mc-remailer-choose-first (n &optional l)
+  (cond
+   ((= n 0) nil)
+   ((null l) (mc-remailer-choose-first n mc-remailer-internal-ranking))
+   (t (cons (car l) (mc-remailer-choose-first (1- n) (cdr l))))))
+
+(defun mc-remailer-choose-chain (n)
+  (if (null mc-remailer-internal-ranking)
+      (error "No ranking information, cannot choose the %d best remailer%s"
+	     n (if (> n 1) "s" "")))
+  (append (shuffle-vector (vconcat (mc-remailer-choose-first n)))
+	  nil))
+
+;;}}}
+;;{{{ Canonicalization function
+
+(defun mc-remailer-canonicalize-elmt (elmt chains-alist)
+  (cond
+   ((mc-remailerp elmt) (list elmt))
+   ((stringp elmt)
+    (mc-remailer-canonicalize-chain (cdr (assoc elmt chains-alist))
+				    chains-alist))
+   ((integerp elmt)
+    (mc-remailer-canonicalize-chain (mc-remailer-choose-chain elmt)
+				    chains-alist))
+   (t (mc-remailer-canonicalize-chain (eval elmt) chains-alist))))
+
+(defun mc-remailer-canonicalize-chain (chain &optional chains-alist)
+  ;; Canonicalize a remailer chain with respect to CHAINS-ALIST.
+  ;; That is, use CHAINS-ALIST to resolve strings.
+  ;; Here is where we implement the functionality described in
+  ;; the documentation for the variable `mc-remailer-user-chains'.
+  (if (null chains-alist)
+      (setq chains-alist (mc-remailer-make-chains-alist)))
+  (cond
+   ((null chain) nil)
+   ;; Handle case where chain is actually a string or a single
+   ;; remailer.
+   ((or (stringp chain) (mc-remailerp chain) (integerp chain))
+    (mc-remailer-canonicalize-elmt chain chains-alist))
+   (t
+    (let ((first (elt chain 0))
+	  (rest (cdr (append chain nil))))
+      (append
+       (mc-remailer-canonicalize-elmt first chains-alist)
+       (mc-remailer-canonicalize-chain rest chains-alist))))))
+
+;;}}}
+;;{{{ Auxiliaries for mail header munging
+
+(defsubst mc-nuke-field (field &optional bounds)
+  ;; Delete all fields exactly matching regexp FIELD from header,
+  ;; bounded by BOUNDS.  Default is entire visible region of buffer.
+  (mc-get-fields field bounds t))
+
+(defun mc-replace-field (field-name replacement header)
+  (save-excursion
+    (save-restriction
+      (if (not (string-match "^[ \t]" replacement))
+	  (setq replacement (concat " " replacement)))
+      (if (not (string-match "\n$" replacement))
+	  (setq replacement (concat replacement "\n")))
+      (let ((case-fold-search t)
+	    (field-regexp (regexp-quote field-name)))
+	(narrow-to-region (car header) (cdr header))
+	(goto-char (point-min))
+	(re-search-forward
+	 (concat "^" field-regexp ":" mc-field-body-regexp)
+	 nil t)
+	(mc-nuke-field field-regexp header)
+	(insert field-name ":" replacement)))))
+
+(defun mc-find-main-header (&optional ignored)
+  ;; Find the main header of the mail message; return as a pair of
+  ;; markers (START . END).
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward
+     (concat "^" (regexp-quote mail-header-separator) "\n"))
+    (forward-line -1)
+    (cons (copy-marker (point-min)) (copy-marker (point)))))
+		
+(defun mc-find-colon-header (&optional insert)
+  ;; Find the header with a "::" immediately after the
+  ;; mail-header-separator.  Return region enclosing header.  Optional
+  ;; arg INSERT means insert the header if it does not exist already.
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward
+     (concat "^" (regexp-quote mail-header-separator) "\n"))
+    (if (or (and (looking-at "::\n") (forward-line 1))
+	    (and insert
+		 (progn
+		   (insert-before-markers "::\n\n")
+		   (forward-line -1))))
+	(let ((start (point)))
+	  (re-search-forward "^$" nil 'move)
+	  (cons (copy-marker start) (copy-marker (point)))))))
+
+(defun mc-find-hash-header (&optional insert)
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward
+     (concat "^" (regexp-quote mail-header-separator) "\n"))
+    (if (or (and (looking-at "##\n") (forward-line 1))
+	    (and (looking-at "::\n")
+		 (re-search-forward "^\n" nil 'move)
+		 (looking-at "##\n")
+		 (forward-line 1))
+	    (and insert
+		 (progn
+		   (insert-before-markers "##\n\n")
+		   (forward-line -1))))
+	(let ((start (point)))
+	  (re-search-forward "^$" nil 'move)
+	  (cons (copy-marker start) (copy-marker (point)))))))
+
+
+(defsubst mc-replace-main-field (field replacement)
+  (mc-replace-field field replacement (mc-find-main-header t)))
+
+(defsubst mc-replace-hash-field (field replacement)
+  (mc-replace-field field replacement (mc-find-hash-header t)))
+
+(defsubst mc-replace-colon-field (field replacement)
+  (mc-replace-field field replacement (mc-find-colon-header t)))
+
+(defun mc-recipient-is-remailerp ()
+  (let ((to (mc-get-fields "To" (mc-find-main-header))))
+    (and to
+	 (string-match (regexp-quote mc-remailer-tag) (cdr (car to))))))
+
+;;}}}
+;;{{{ Pre-encryption and post-encryption hook defaults
+
+(defun mc-generic-post-encrypt-function (remailer)
+  (let ((main-header (mc-find-main-header))
+	(colon-header (mc-find-colon-header t)))
+    (mc-replace-field "Encrypted" "PGP" colon-header)
+    (mc-replace-field
+     "To"
+     (concat (mc-remailer-address remailer) " " mc-remailer-tag)
+     main-header)))
+
+(defun mc-generic-pre-encrypt-function (remailer)
+  (let ((addr (mc-remailer-address remailer))
+	(props (mc-remailer-properties remailer))
+	(main-header (mc-find-main-header))
+	(colon-header (mc-find-colon-header t))
+	to to-field preserved-regexp preserved)
+
+    (setq preserved-regexp
+	  (mc-disjunction-regexp mc-remailer-preserved-headers))
+    (setq preserved (mc-get-fields preserved-regexp main-header t))
+    (if preserved (goto-char (cdr (mc-find-hash-header t))))
+    (mapcar (function
+	     (lambda (c)
+	       (insert (car c) ":"
+		       (mc-eliminate-continuation-lines (cdr c)))))
+	    preserved)
+
+    (if (and (mc-find-hash-header) (not (member "hash" props)))
+	(error "Remailer %s does not support hashmarks" addr))
+
+    (if (mc-get-fields "Newsgroups" main-header)
+	(cond ((not (member "post" props))
+	       (error "Remailer %s does not support posting" addr))
+	      ((not (member "hash" props))
+	       (error "Remailer %s does not support hashmarks" addr))
+	      (t (mc-rewrite-news-to-mail remailer)))
+      (and (featurep 'mailalias)
+	   (not (featurep 'mail-abbrevs))
+	   mail-aliases
+	   (expand-mail-aliases (car main-header) (cdr main-header)))
+      (setq to (mc-strip-addresses
+		(mapcar 'cdr (mc-get-fields "To" main-header))))
+      (if (string-match "," to)
+	  (error "Remailer %s does not support multiple recipients." addr))
+      (setq to-field
+	    (if (mc-get-fields "From" colon-header)
+		"Send-To"
+	      (cond
+	       ((member "eric" props) "Anon-Send-To")
+	       ((member "cpunk" props) "Request-Remailing-To")
+	       (t (error "Remailer %s is not type-1" addr)))))
+      (mc-replace-field to-field to colon-header)
+      (mc-nuke-field "Reply-to" main-header))))
+	
+;;}}}
+;;{{{ Misc. random
+
+(defun mc-disjunction-regexp (regexps)
+  ;; Take a list of regular expressions and return a single
+  ;; regular expression which matches anything that any of the
+  ;; original regexps match.
+  (concat "\\("
+	  (mapconcat 'identity regexps "\\)\\|\\(")
+	  "\\)"))
+
+(defun mc-user-mail-address ()
+  "Figure out the user's Email address as best we can."
+  (mc-strip-address
+   (cond ((and (boundp 'gnus-user-from-line)
+	       (stringp gnus-user-from-line))
+	  gnus-user-from-line)
+	 ((stringp mail-default-reply-to) mail-default-reply-to)
+	 ((boundp 'user-mail-address) user-mail-address)
+	 (t (concat (user-login-name) "@" (system-name))))))
+
+(defun mc-eliminate-continuation-lines (string)
+  (while (string-match "\n[\t ]+" string)
+    (setq string (replace-match " " t nil string)))
+  string)
+
+(defun mc-remailer-make-chains-alist ()
+  (if (null mc-remailer-internal-chains)
+      (mc-reread-levien-file))
+  (append mc-remailer-internal-chains mc-remailer-user-chains))
+
+;;;###autoload
+(defun mc-remailer-insert-pseudonym ()
+  "Insert pseudonym as a From field in the hash-mark header.
+
+See the documentation for the variable `mc-remailer-pseudonyms' for
+more information."
+  (interactive)
+  (let ((completion-ignore-case t)
+	pseudonym)
+    (setq pseudonym
+	  (cond ((null mc-remailer-pseudonyms)
+		 (read-from-minibuffer "Pseudonym: "))
+		(t
+		 (completing-read "Pseudonym: "
+				  (mapcar 'list mc-remailer-pseudonyms)))))
+    (if (not (string-match "\\S +@\\S +" pseudonym))
+	(setq pseudonym (concat pseudonym " <x@x.x>")))
+    (mc-replace-colon-field "From" pseudonym)))
+
+;;}}}
+;;{{{ Mixmaster support
+(defvar mc-mixmaster-path nil
+  "*Path to the Mixmaster binary.  If defined, Mixmaster chains will
+be passed to this program for rewriting.")
+
+(defvar mc-mixmaster-list-path nil
+  "*Path to the Mixmaster type2.list file.")
+
+(defun mc-demix (&rest chain)
+  "Use arguments as a remailer-list and return a new list with the
+\"mix\" property removed from all the elements."
+  (mapcar (function (lambda (r) (mc-remailer-remove-property r "mix")))
+	  (mc-remailer-canonicalize-chain chain)))
+
+(defun mc-mixmaster-process (beg end recipients preserved mix-chain)
+  ;; Run a region through Mixmaster.
+  (let (ret)
+    (if (not (markerp end))
+	(setq end (copy-marker end)))
+    (goto-char beg)
+    (mapcar (function (lambda (x) (insert x ?\n))) recipients)
+    (insert ?\n)
+    (mapcar (function (lambda (x) (insert x))) preserved)
+    (insert ?\n)
+    (setq mix-chain (mapcar (function (lambda (x) (format "%d" x))) mix-chain))
+    ;; Handle case of empty message
+    (if (< end (point)) (setq end (point)))
+    (setq ret
+	  (apply 'call-process-region beg end mc-mixmaster-path t t nil
+		 "-f" "-o" "stdout" "-l" mix-chain))
+    (if (not (eq ret 0)) (error "Mixmaster barfed."))
+    (goto-char beg)
+    (re-search-forward "^::$")
+    (delete-region beg (match-beginning 0))))
+
+(defun mc-mixmaster-build-alist (&optional n)
+  ;; Construct an alist mapping Mixmaster Email addresses to integers.
+  ;; FIXME; this is terrible
+  (let (buf)
+    (save-excursion
+      (unwind-protect
+	  (progn
+	    (setq n (or n 1))
+	    (setq buf (find-file-noselect mc-mixmaster-list-path))
+	    (set-buffer buf)
+	    (if (re-search-forward "^[^ \t]+[ \t]+\\([^ \t]+\\)" nil t)
+		(cons (cons (buffer-substring-no-properties
+			     (match-beginning 1) (match-end 1))
+			    n)
+		    (mc-mixmaster-build-alist (+ n 1)))))
+	(if buf (kill-buffer buf))))))
+
+(defvar mc-mixmaster-alist nil)
+
+(defsubst mc-mixmaster-alist ()
+  (or mc-mixmaster-alist
+      (setq mc-mixmaster-alist (mc-mixmaster-build-alist))))
+
+(defun mc-mixmaster-translate-chain (chain)
+  ;; Take a chain of Mixmaster remailers and convert it to the list
+  ;; of integers which represents them.
+  (if (or (null chain)
+	  (not (member "mix" (mc-remailer-properties (car chain)))))
+      nil
+    (cons (cdr (assoc (mc-strip-address (mc-remailer-address (car chain)))
+		      (mc-mixmaster-alist)))
+	  (mc-mixmaster-translate-chain (cdr chain)))))
+
+(defun mc-mixmaster-skip (chain)
+  ;; Return the largest possible suffix of CHAIN whose first element
+  ;; is not a Mixmaster.
+  (cond ((null chain) nil)
+	((not (member "mix" (mc-remailer-properties (car chain))))
+	 chain)
+	(t (mc-mixmaster-skip (cdr chain)))))
+
+(defun mc-rewrite-for-mixmaster (chain &optional pause)
+  ;; Rewrite the current mail buffer for a chain of Mixmasters.
+  (let ((mix-chain (mc-mixmaster-translate-chain chain))
+	(main-header (mc-find-main-header))
+	(colon-header (mc-find-colon-header))
+	(hash-header (mc-find-hash-header))
+	recipients preserved newsgroups first last rest preserved-regexp)
+
+    ;; Figure out FIRST and LAST. FIRST is the first Mixmaster in the
+    ;; chain.  LAST is the last.
+    (setq first (car chain)
+	  rest chain)
+    (while (and rest (member "mix" (mc-remailer-properties (car rest))))
+      (setq last (car rest)
+	    rest (cdr rest)))
+    
+    ;; If recipient is not a remailer, deal with hashmark and colon
+    ;; headers and get rid of them.
+    (if (mc-recipient-is-remailerp)
+	nil
+      (if hash-header
+	  (progn
+	    (setq preserved (mc-get-fields nil hash-header))
+	    (goto-char (car hash-header))
+	    (forward-line -1)
+	    (delete-region (point) (+ (cdr hash-header) 1))))
+      ;; Preserve pseduonym line...
+      (if colon-header
+	  (progn
+	    (setq preserved
+		  (append (mc-get-fields "From" colon-header) preserved))
+	    (goto-char (car colon-header))
+	    (forward-line -1)
+	    (delete-region (point) (+ (cdr colon-header) 1)))))
+    
+    ;; Expand aliases and get recipients.
+    (and (featurep 'mailalias)
+	 (not (featurep 'mail-abbrevs))
+	 mail-aliases
+	 (expand-mail-aliases (car main-header) (cdr main-header)))
+    (setq recipients
+	  (mc-cleanup-recipient-headers
+	   (mapconcat 'cdr (mc-get-fields "To" main-header t) ", ")))
+    (setq newsgroups (mc-get-fields "Newsgroups" nil t))
+    ;; Mixmaster does not support posting...
+;;;    (if (and newsgroups
+;;;	     (not (member "post" (mc-remailer-properties last))))
+    (if newsgroups
+	(error "Remailer %s does not support posting"
+	       (mc-remailer-address last)))
+    (setq
+     recipients
+     (append (mapcar
+	      (function (lambda (c) (concat "Post:" (cdr c)))) newsgroups)
+	     recipients))
+
+    (setq
+     preserved-regexp
+     (mc-disjunction-regexp (cons "Subject" mc-remailer-preserved-headers)))
+
+    (setq preserved
+	  (append (mc-get-fields preserved-regexp main-header t) preserved))
+
+    ;; Convert preserved header alist to simple list of strings
+    (setq preserved
+	  (mapcar
+	   (function
+	    (lambda (c)
+	      (concat (car c) ":"
+		      (mc-eliminate-continuation-lines (cdr c)))))
+	   preserved))
+
+    ;; Do the conversion
+    (goto-char (cdr main-header))
+    (forward-line 1)
+    (mc-mixmaster-process (point) (point-max) recipients preserved
+			  mix-chain)
+
+    (mc-replace-field "To"
+		      (concat
+		       (mc-remailer-address first) " " mc-remailer-tag)
+		      main-header)))
+
+;;}}}
+;;{{{ High level message rewriting
+
+(defun mc-rewrite-news-to-mail (remailer)
+  (let ((main-header (mc-find-main-header))
+	newsgroups)
+    (setq newsgroups (mc-get-fields "Newsgroups" main-header t))
+    (mc-replace-colon-field "Post-To" (cdr (car newsgroups)))
+    (mail-mode)))
+
+(defun mc-rewrite-for-remailer (remailer &optional pause)
+  ;; Rewrite the current mail buffer for a single remailer.  This
+  ;; includes running the pre-encryption hooks, modifying the To:
+  ;; field, encrypting with the remailer's public key, and running the
+  ;; post-encryption hooks.
+  (let ((addr (mc-remailer-address remailer))
+	(main-header (mc-find-main-header)))
+    ;; If recipient is already a remailer, make sure the "::" and "##"
+    ;; headers get to it
+    (if (mc-recipient-is-remailerp)
+	(progn
+	  (goto-char (cdr main-header))
+	  (forward-line 1)
+	  (insert "::\n\n")))
+
+    (mapcar
+     (function (lambda (hook) (funcall hook remailer)))
+     (mc-remailer-pre-encrypt-hooks remailer))
+
+    ;; Move "Subject" lines down.
+    (goto-char (car (mc-find-colon-header t)))
+    (mapcar
+     (function (lambda (f) (insert (car f) ":" (cdr f))))
+     (mc-get-fields "Subject" main-header t))
+
+    (if pause
+	(let ((cursor-in-echo-area t))
+	  (message "SPC to encrypt for %s : " addr)
+	  (read-char-exclusive)))
+    (setq main-header (mc-find-main-header))
+    (goto-char (cdr main-header))
+    (forward-line 1)
+    (if (let ((mc-pgp-always-sign 'never)
+	      (mc-encrypt-for-me nil))
+	  (mc-encrypt-message (mc-remailer-userid remailer) nil (point)))
+	(progn
+	  (mapcar
+	   (function (lambda (hook) (funcall hook remailer)))
+	   (mc-remailer-post-encrypt-hooks remailer))
+	  (mc-nuke-field "Comment")
+	  (mc-nuke-field "From"))
+      (error "Unable to encrypt message to %s"
+	     (mc-remailer-userid remailer)))))
+
+(defun mc-rewrite-for-chain (chain &optional pause)
+  ;; Rewrite the current buffer for a chain of remailers.
+  ;; CHAIN must be in canonical form.
+  (let (rest)
+    (if mc-mixmaster-path
+	(setq rest (mc-mixmaster-skip chain))
+      (setq rest chain))
+    (if (null chain) nil
+      (mc-rewrite-for-chain
+       (if (eq rest chain) (cdr rest) rest) pause)
+      (if (eq rest chain)
+	  (mc-rewrite-for-remailer (car chain) pause)
+	(mc-rewrite-for-mixmaster chain pause)))))
+
+(defun mc-unparse-chain (chain)
+  ;; Unparse CHAIN into a string suitable for printing.
+  (if (null chain)
+      nil
+    (concat (mc-remailer-address (car chain)) "\n"
+	    (mc-unparse-chain (cdr chain)))))
+
+(defun mc-disallow-field (field &optional header)
+  (let ((case-fold-search t))
+    (if (null header)
+	(setq header (mc-find-main-header)))
+    (goto-char (car header))
+    (if (re-search-forward (concat "^" (regexp-quote field) ":")
+			  (cdr header) t)
+	
+	(progn
+	  (goto-char (match-beginning 0))
+	  (error "Cannot use a %s field." field)))))
+
+;;;###autoload
+(defun mc-remailer-encrypt-for-chain (&optional pause)
+  "Encrypt message for a remailer chain, prompting for chain to use.
+
+With \\[universal-argument], pause before each encryption."
+  (interactive "P")
+  (let ((chains (mc-remailer-make-chains-alist))
+	(buffer (get-buffer-create mc-buffer-name))
+	chain-name chain)
+    (mc-disallow-field "CC")
+    (mc-disallow-field "FCC")
+    (mc-disallow-field "BCC")
+    (setq chain-name
+	  (completing-read
+	   "Choose a remailer or chain: " chains nil 'strict-match))
+    (setq chain
+	  (mc-remailer-canonicalize-chain
+	   (cdr (assoc chain-name chains))
+	   chains))
+    (mc-rewrite-for-chain chain pause)
+    (if chain
+	(save-excursion
+	  (set-buffer buffer)
+	  (erase-buffer)
+	  (insert "Rewritten for chain `" chain-name "':\n\n"
+		  (mc-unparse-chain chain))
+	  (message "Done.  See %s buffer for details." mc-buffer-name)))))
+
+;;}}}
+;;{{{ Response block generation
+
+;;;###autoload
+(defun mc-remailer-insert-response-block (&optional arg)
+  "Insert response block at point, prompting for chain to use.
+
+With \\[universal-argument], enter a recursive edit of the innermost
+layer of the block before encrypting it."
+  (interactive "p")
+  (let (buf main-header to addr block lines)
+    (save-excursion
+      (setq buf
+	    (mc-remailer-make-response-block (if (> arg 1) t)))
+      (set-buffer buf)
+      (setq main-header (mc-find-main-header))
+      (setq to (cdr (car (mc-get-fields "To" main-header))))
+      (setq addr (concat "<" (mc-strip-address to) ">"))
+      (goto-char (cdr main-header))
+      (forward-line 1)
+      (setq block (buffer-substring-no-properties
+		   (point) (point-max))
+	    lines (count-lines (point) (point-max)))
+      (kill-buffer buf))
+    (let ((opoint (point)))
+      (insert (funcall mc-remailer-user-response-block
+		       addr lines block))
+      (goto-char opoint))
+    (mc-nuke-field "Reply-to" (mc-find-main-header))
+    (mc-replace-hash-field "Reply-to" addr)))
+
+(defun mc-remailer-make-response-block (&optional recurse)
+  ;; Return a buffer which contains a response block
+  ;; for the user, and a To: header for the remailer to use.
+  (let ((buf (generate-new-buffer " *Remailer Response Block*"))
+	(original-buf (current-buffer))
+	(mc-mixmaster-path nil)
+	all-headers included-regexp included)
+    (setq all-headers (mc-find-main-header))
+    (setcdr all-headers
+	    (max
+	     (cdr all-headers)
+	     (or (cdr-safe (mc-find-colon-header)) 0)
+	     (or (cdr-safe (mc-find-hash-header)) 0)))
+    (save-excursion
+      (setq
+       included-regexp
+       (mc-disjunction-regexp mc-response-block-included-headers))
+      (setq included (mc-get-fields included-regexp all-headers))
+      (set-buffer buf)
+      (insert "To: " (mc-user-mail-address) "\n" mail-header-separator "\n")
+      (insert ";; Response block created " (current-time-string) "\n")
+      (mapcar (function (lambda (c) (insert "; " (car c) ":" (cdr c))))
+	      included)
+      (if recurse
+	  (progn
+	    (switch-to-buffer buf)
+	    (message "Editing response block ; %s when done."
+		     (substitute-command-keys "\\[exit-recursive-edit]"))
+	    (recursive-edit)))
+      (set-buffer buf)
+      (mc-remailer-encrypt-for-chain)
+      (switch-to-buffer original-buf))
+    buf))
+
+;;}}}