#!/usr/bin/ruby require 'optparse' require 'thread' Thread.abort_on_exception = true GNUPG = "/usr/bin/gpg" $program_name = File.basename($0, '.*') class GnuPG @@my_keys = nil def GnuPG.readwrite3(intxt, infd, stdoutfd, stderrfd, statusfd) outtxt, stderrtxt, statustxt = '' thread_in = Thread.new { infd.print intxt infd.close } thread_out = Thread.new { outtxt = stdoutfd.read stdoutfd.close } thread_err = Thread.new { errtxt = stderrfd.read stderrfd.close } thread_status = Thread.new { statustxt = statusfd.read statusfd.close } if (statusfd) thread_in.join thread_out.join thread_err.join thread_status.join if thread_status return outtxt, stderrtxt, statustxt end def GnuPG.gpgcall(intxt, args, require_success = false) inR, inW = IO.pipe outR, outW = IO.pipe errR, errW = IO.pipe statR, statW = IO.pipe pid = Kernel.fork do inW.close outR.close errR.close statR.close STDIN.reopen(inR) STDOUT.reopen(outW) STDERR.reopen(errW) exec(GNUPG, "--status-fd=#{statW.fileno}", *args) raise ("Calling gnupg failed") end inR.close outW.close errW.close statW.close (outtxt, stderrtxt, statustxt) = readwrite3(intxt, inW, outR, errR, statR); wpid, status = Process.waitpid2 pid throw "Unexpected pid: #{pid} vs #{wpid}" unless pid == wpid throw "Process has not exited!?" unless status.exited? throw "gpg call did not exit sucessfully" if (require_success and status.exitstatus != 0) return outtxt, stderrtxt, statustxt, status.exitstatus end def GnuPG.init_keys() return if @@my_keys (outtxt, stderrtxt, statustxt) = GnuPG.gpgcall('', %w{--fast-list-mode --with-colons --list-secret-keys}, true) @@my_keys = [] outtxt.split("\n").each do |line| parts = line.split(':') if (parts[0] == "ssb" or parts[0] == "sec") @@my_keys.push parts[4] end end end def GnuPG.get_my_keys() init_keys @@my_keys end end class EncryptedFile attr_reader :readable, :encrypted def initialize(filename) content = File.read(filename) (outtxt, stderrtxt, statustxt) = GnuPG.gpgcall(content, %w{--with-colons --no-default-keyring --secret-keyring=/dev/null --keyring=/dev/null}) @encrypted = !(statustxt =~ /\[GNUPG:\] NODATA/) if @encrypted @readers = EncryptedFile.list_readers(statustxt) @readable = EncryptedFile.determine_readable(@readers) end end def EncryptedFile.determine_readable(readers) GnuPG.get_my_keys.each do |keyid| return true if readers.include?(keyid) end return false end def EncryptedFile.list_readers(statustxt) readers = [] statustxt.split("\n").each do |line| m = /^\[GNUPG:\] ENC_TO ([0-9A-F]+)/.match line next unless m readers.push m[1] end return readers end end class Ls def help(parser, code=0, io=STDOUT) io.puts "Usage: #{$program_name} ls [ ...]" io.puts parser.summarize io.puts "Lists the contents of the given directory/directories, or the current" io.puts "directory if none is given. For each file show whether it is PGP-encrypted" io.puts "file, and if yes whether we can read it." exit(code) end def ls_dir(dirname) begin dir = Dir.open(dirname) rescue Exception => e STDERR.puts e return end puts "#{dirname}:" Dir.chdir(dirname) do dir.sort.each do |filename| next if (filename =~ /^\./) and not (@all >= 3) stat = File::Stat.new(filename) if stat.symlink? puts "(sym) #{filename}" if (@all >= 2) elsif stat.directory? puts "(dir) #{filename}" if (@all >= 2) elsif !stat.file? puts "(other) #{filename}" if (@all >= 2) else f = EncryptedFile.new(filename) if f.encrypted if f.readable puts "(ok) #{filename}" else puts "(locked) #{filename}" if (@all >= 1) end else puts "(file) #{filename}" if (@all >= 2) end end end end end def initialize() @all = 0 ARGV.options do |opts| opts.on_tail("-h", "--help" , "Display this help screen") { help(opts) } opts.on_tail("-a", "--all" , "Show all files (use up to 3 times to show even more than all)") { @all = @all+1 } opts.parse! end dirs = ARGV dirs.push('.') unless dirs.size > 0 dirs.each { |dir| ls_dir(dir) } end end case ARGV.shift when 'ls': Ls.new else STDERR.puts "What!?" end # vim:set shiftwidth=2: # vim:set et: # vim:set ts=2: