sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 4812245a2f975370290fb4235b05e9eca793e318
parent 1b2c7b1dfee5a3959da702c17d104ead5e2f4bab
Author: William Morgan <wmorgan-sup@masanjin.net>
Date:   Sun, 11 Oct 2009 13:23:25 -0700

Merge branch 'interactive-crypto' into next

Diffstat:
M lib/sup/buffer.rb | 1 +
M lib/sup/crypto.rb | 129 +++++++++++++++++++++++++++++++++++++++++++++----------------------------------
2 files changed, 75 insertions(+), 55 deletions(-)
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
@@ -757,6 +757,7 @@ EOS
   end
 
 private
+
   def default_status_bar buf
     " [#{buf.mode.name}] #{buf.title}   #{buf.mode.status}"
   end
diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb
@@ -15,15 +15,14 @@ class CryptoManager
     @mutex = Mutex.new
 
     bin = `which gpg`.chomp
-    @cmd =
-      case bin
-      when /\S/
-        debug "crypto: detected gpg binary in #{bin}"
-        "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
-      else
-        debug "crypto: no gpg binary detected"
-        nil
-      end
+    @cmd = case bin
+    when /\S/
+      debug "crypto: detected gpg binary in #{bin}"
+      "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
+    else
+      debug "crypto: no gpg binary detected"
+      nil
+    end
   end
 
   def have_crypto?; !@cmd.nil? end
@@ -33,15 +32,19 @@ class CryptoManager
     payload_fn.write format_payload(payload)
     payload_fn.close
 
-    output = run_gpg "--output - --armor --detach-sign --textmode --local-user '#{from}' #{payload_fn.path}"
+    sig_fn = Tempfile.new "redwood.signature"; sig_fn.close
 
-    raise Error, (output || "gpg command failed: #{cmd}") unless $?.success?
+    message = run_gpg "--output #{sig_fn.path} --yes --armor --detach-sign --textmode --local-user '#{from}' #{payload_fn.path}", :interactive => true
+    unless $?.success?
+      info "Error while running gpg: #{message}"
+      raise Error, "GPG command failed. See log for details."
+    end
 
     envelope = RMail::Message.new
     envelope.header["Content-Type"] = 'multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha1'
 
     envelope.add_part payload
-    signature = RMail::Message.make_attachment output, "application/pgp-signature", nil, "signature.asc"
+    signature = RMail::Message.make_attachment IO.read(sig_fn.path), "application/pgp-signature", nil, "signature.asc"
     envelope.add_part signature
     envelope
   end
@@ -51,15 +54,20 @@ class CryptoManager
     payload_fn.write format_payload(payload)
     payload_fn.close
 
+    encrypted_fn = Tempfile.new "redwood.encrypted"; encrypted_fn.close
+
     recipient_opts = (to + [ from ] ).map { |r| "--recipient '<#{r}>'" }.join(" ")
     sign_opts = sign ? "--sign --local-user '#{from}'" : ""
-    gpg_output = run_gpg "--output - --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}"
-    raise Error, (gpg_output || "gpg command failed: #{cmd}") unless $?.success?
+    message = run_gpg "--output #{encrypted_fn.path} --yes --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}", :interactive => true
+    unless $?.success?
+      info "Error while running gpg: #{message}"
+      raise Error, "GPG command failed. See log for details."
+    end
 
     encrypted_payload = RMail::Message.new
     encrypted_payload.header["Content-Type"] = "application/octet-stream"
     encrypted_payload.header["Content-Disposition"] = 'inline; filename="msg.asc"'
-    encrypted_payload.body = gpg_output
+    encrypted_payload.body = IO.read(encrypted_fn.path)
 
     control = RMail::Message.new
     control.header["Content-Type"] = "application/pgp-encrypted"
@@ -111,46 +119,48 @@ class CryptoManager
     payload_fn.write payload.to_s
     payload_fn.close
 
-    output = run_gpg "--decrypt #{payload_fn.path}"
+    output_fn = Tempfile.new "redwood.output"
+    output_fn.close
 
-    if $?.success?
-      decrypted_payload, sig_lines = if output =~ /\A(.*?)((^gpg: .*$)+)\Z/m
-        [$1, $2]
-      else
-        [output, nil]
-      end
+    message = run_gpg "--output #{output_fn.path} --yes --decrypt #{payload_fn.path}", :interactive => true
 
-      sig = if sig_lines # encrypted & signed
-        if sig_lines =~ /^gpg: (Good signature from .*$)/
-          Chunk::CryptoNotice.new :valid, $1, sig_lines.split("\n")
-        else
-          Chunk::CryptoNotice.new :invalid, $1, sig_lines.split("\n")
-        end
-      end
+    unless $?.success?
+      info "Error while running gpg: #{message}"
+      return Chunk::CryptoNotice.new(:invalid, "This message could not be decrypted", message.split("\n"))
+    end
 
-      # This is gross. This decrypted payload could very well be a multipart
-      # element itself, as opposed to a simple payload. For example, a
-      # multipart/signed element, like those generated by Mutt when encrypting
-      # and signing a message (instead of just clearsigning the body).
-      # Supposedly, decrypted_payload being a multipart element ought to work
-      # out nicely because Message::multipart_encrypted_to_chunks() runs the
-      # decrypted message through message_to_chunks() again to get any
-      # children. However, it does not work as intended because these inner
-      # payloads need not carry a MIME-Version header, yet they are fed to
-      # RMail as a top-level message, for which the MIME-Version header is
-      # required. This causes for the part not to be detected as multipart,
-      # hence being shown as an attachment. If we detect this is happening,
-      # we force the decrypted payload to be interpreted as MIME.
-      msg = RMail::Parser.read(decrypted_payload)
-      if msg.header.content_type =~ %r{^multipart/} and not msg.multipart?
-        decrypted_payload = "MIME-Version: 1.0\n" + decrypted_payload
-        msg = RMail::Parser.read(decrypted_payload)
-      end
-      notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
-      [notice, sig, msg]
-    else
-      Chunk::CryptoNotice.new :invalid, "This message could not be decrypted", output.split("\n")
+    output = IO.read output_fn.path
+
+    ## there's probably a better way to do this, but we're using the output to
+    ## look for a valid signature being present.
+
+    sig = case message
+    when /^gpg: (Good signature from .*$)/i
+      Chunk::CryptoNotice.new :valid, $1, message.split("\n")
+    when /^gpg: (Bad signature from .*$)/i
+      Chunk::CryptoNotice.new :invalid, $1, message.split("\n")
     end
+
+    # This is gross. This decrypted payload could very well be a multipart
+    # element itself, as opposed to a simple payload. For example, a
+    # multipart/signed element, like those generated by Mutt when encrypting
+    # and signing a message (instead of just clearsigning the body).
+    # Supposedly, decrypted_payload being a multipart element ought to work
+    # out nicely because Message::multipart_encrypted_to_chunks() runs the
+    # decrypted message through message_to_chunks() again to get any
+    # children. However, it does not work as intended because these inner
+    # payloads need not carry a MIME-Version header, yet they are fed to
+    # RMail as a top-level message, for which the MIME-Version header is
+    # required. This causes for the part not to be detected as multipart,
+    # hence being shown as an attachment. If we detect this is happening,
+    # we force the decrypted payload to be interpreted as MIME.
+    msg = RMail::Parser.read output
+    if msg.header.content_type =~ %r{^multipart/} && !msg.multipart?
+      output = "MIME-Version: 1.0\n" + output
+      msg = RMail::Parser.read output
+    end
+    notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
+    [notice, sig, msg]
   end
 
 private
@@ -169,10 +179,19 @@ private
     payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "")
   end
 
-  def run_gpg args
-    cmd = "#{@cmd} #{args} 2> /dev/null"
-    output = `#{cmd}`
-    output
+  def run_gpg args, opts={}
+    cmd = "#{@cmd} #{args}"
+    if opts[:interactive] && BufferManager.instantiated?
+      output_fn = Tempfile.new "redwood.output"
+      output_fn.close
+      cmd += " > #{output_fn.path} 2> /dev/null"
+      debug "crypto: running: #{cmd}"
+      BufferManager.shell_out cmd
+      IO.read(output_fn.path) rescue "can't read output"
+    else
+      debug "crypto: running: #{cmd}"
+      `#{cmd} 2> /dev/null`
+    end
   end
 end
 end