From 02ea7caafedefefff56591a3a7fbf8d7606c7291 Mon Sep 17 00:00:00 2001 From: Alyssa Ross Date: Tue, 5 Nov 2019 11:52:46 +0000 Subject: Initial commit --- unpgpmime | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 unpgpmime (limited to 'unpgpmime') diff --git a/unpgpmime b/unpgpmime new file mode 100755 index 0000000..2e73bc7 --- /dev/null +++ b/unpgpmime @@ -0,0 +1,93 @@ +#!/usr/bin/ruby + +# Copyright 2019 Alyssa Ross +# +# 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 3 of the License, 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 this program. If not, see . + +at_exit do + case $! + when nil, SystemExit + else + $stderr.puts "unpgpmime: #$!" + exit! 1 + end +end + +require "mail" +require "open3" +require "optparse" + +OptionParser.new do |opts| + opts.banner = <<~BANNER + Usage: #$0 [OPTION]... [FILE] + Strip PGP/MIME encryption from FILE (standard input by default). + + BANNER + + opts.on "-h", "--help", "show this usage display" do + puts opts + exit + end +end.parse! + +def decrypt(data) + result, status = Open3.capture2(*%w[gpg --no-batch --decrypt], stdin_data: data) + fail "gpg failed with exit code #{status.to_i}" unless status.success? + result +end + +def validate_multipart_encrypted(message) + part_types = message.parts.map(&:content_type) + expected_part_types = %w[application/pgp-encrypted application/octet-stream] + unless part_types.difference(expected_part_types).empty? + fail "unexpected or missing parts of multipart/encrypted" + end + + pgp_encrypted_type = "application/pgp-encrypted" + pgp_encrypted = message.parts.find { |p| p.content_type == pgp_encrypted_type } + pgp_content = Mail::Part.new(pgp_encrypted.body) + if pgp_content["Version"].value != "1" + fail "unknown application/pgp-encrypted version" + end +end + +def strip_pgp(part) + return part unless part.content_type =~ %r{\Amultipart/encrypted(;|\z)} + return part unless part.content_type_parameters["protocol"] == "application/pgp-encrypted" + + validate_multipart_encrypted(part) + + new_part = Mail::Part.new + + encrypted = part.parts.find { |p| p.content_type == "application/octet-stream" } + decrypted = Mail::Part.new(decrypt(encrypted.read)) + + encrypted_fields = decrypted.header_fields.group_by(&:name) + part.header_fields.each do |field| + next if encrypted_fields.include?(field.name) + new_part.headers(field.name => field.value) + end + + decrypted.parts.each do |part| + new_part.add_part(strip_pgp(part)) + end + new_part.body = decrypted.body.to_s if new_part.parts.empty? + + new_part +end + +fail "multiple messages are not supported" if ARGV.size > 1 + +message = Mail::Message.new($<.read) +print strip_pgp(message) -- cgit 1.4.1