exec.rb 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. module SecureMailing::PGP::Tool::Exec
  3. extend ActiveSupport::Concern
  4. include SecureMailing::PGP::Tool::Error::Handler
  5. include SecureMailing::PGP::Tool::Exec::Agent
  6. included do # rubocop:disable Metrics/BlockLength
  7. attr_accessor :gnupg_home
  8. def with_private_keyring
  9. dir = Dir.mktmpdir('zammad-gnupg-keyring', Rails.root.join('tmp'))
  10. begin
  11. @gnupg_home = dir
  12. yield(self)
  13. ensure
  14. kill_agent
  15. FileUtils.rm_rf(@gnupg_home)
  16. @gnupg_home = nil
  17. end
  18. end
  19. def gpg(command, options: [], arguments: [], stdin: nil, passphrase: nil)
  20. raise __("Use 'with_private_keyring' to create a private keyring or set @gnupg_home before calling gpg.") if !@gnupg_home
  21. args = %w[--batch --yes --no-tty --verbose --status-fd 2] + options + ["--#{command}"] + arguments
  22. env = {
  23. 'LC_ALL' => 'C', # Force use of English
  24. 'GNUPGHOME' => @gnupg_home # Create/use a temporary keyring
  25. }
  26. run(args, env, stdin, passphrase)
  27. end
  28. private
  29. def binary_path
  30. return @which if @which
  31. if ENV['GPG_PATH'] && File.executable?(ENV['GPG_PATH'])
  32. @which = ENV['GPG_PATH']
  33. return @which
  34. end
  35. ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
  36. gpg = File.join(path, 'gpg')
  37. if File.executable?(gpg)
  38. @which = gpg
  39. return @which
  40. end
  41. end
  42. raise Errno::ENOENT, 'gpg: command not found'
  43. end
  44. def run(args, env, stdin, passphrase)
  45. if passphrase
  46. passphrase_file = Tempfile.new('passphrase')
  47. begin
  48. passphrase_file.write(passphrase)
  49. passphrase_file.close
  50. options = [
  51. '--passphrase-file', passphrase_file.path,
  52. '--pinentry-mode', 'loopback',
  53. ]
  54. args.insert(4, *options)
  55. stdout, stderr, status = Open3.capture3(env, binary_path, *args, stdin_data: stdin, binmode: true)
  56. ensure
  57. passphrase_file.unlink
  58. end
  59. else
  60. stdout, stderr, status = Open3.capture3(env, binary_path, *args, stdin_data: stdin, binmode: true)
  61. end
  62. result!([binary_path] + args, env, stdin, { stdout: stdout, stderr: stderr, status: status })
  63. end
  64. end
  65. end