#!/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)