]> err.no Git - pwstore/blobdiff - pws
mention that one should keep stuff in git
[pwstore] / pws
diff --git a/pws b/pws
index cccb42c2214b7ce913319a9bcbd9c6eea15bebe9..b81da23395cb88ed8de47448ed82dd630542520c 100755 (executable)
--- a/pws
+++ b/pws
@@ -2,7 +2,7 @@
 
 # password store management tool
 
-# Copyright (c) 2008 Peter Palfrader <peter@palfrader.org>
+# Copyright (c) 2008, 2009 Peter Palfrader <peter@palfrader.org>
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -90,7 +90,12 @@ class GnuPG
       STDIN.reopen(inR)
       STDOUT.reopen(outW)
       STDERR.reopen(errW)
-      exec(GNUPG, "--status-fd=#{statW.fileno}",  *args)
+      begin
+        exec(GNUPG, "--status-fd=#{statW.fileno}",  *args)
+      rescue Exception => e
+        outW.puts("[PWSEXECERROR]: #{e}")
+        exit(1)
+      end
       raise ("Calling gnupg failed")
     end
     inR.close
@@ -102,6 +107,10 @@ class GnuPG
     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)
+    if m=/^\[PWSEXECERROR\]: (.*)/.match(outtxt) then
+      STDERR.puts "Could not run GnuPG: #{m[1]}"
+      exit(1)
+    end
     return outtxt, stderrtxt, statustxt, status.exitstatus
   end
 
@@ -185,7 +194,11 @@ def read_input(query, default_yes=true)
 
   while true
     print "#{query} #{append} "
-    i = STDIN.readline.chomp.downcase
+    begin
+      i = STDIN.readline.chomp.downcase
+    rescue EOFError
+      return default_yes
+    end
     if i==""
       return default_yes
     elsif i=="y"
@@ -219,7 +232,7 @@ class GroupConfig
       trusted.push line
     end
 
-    (outtxt, stderrtxt, statustxt, exitstatus) = GnuPG.gpgcall(content, %w{}, true)
+    (outtxt, stderrtxt, statustxt, exitstatus) = GnuPG.gpgcall(content, %w{})
     goodsig = false
     validsig = nil
     statustxt.split("\n").each do |line|
@@ -231,7 +244,12 @@ class GroupConfig
     end
 
     if not goodsig
-      STDERR.puts ".users file is not signed properly"
+      STDERR.puts ".users file is not signed properly.  GnuPG said on stdout:"
+      STDERR.puts outtxt
+      STDERR.puts "and on stderr:"
+      STDERR.puts stderrtxt
+      STDERR.puts "and via statusfd:"
+      STDERR.puts statustxt
       exit(1)
     end
 
@@ -240,6 +258,11 @@ class GroupConfig
       exit(1)
     end
 
+    if not exitstatus==0
+      STDERR.puts "gpg verify failed for .users file"
+      exit(1)
+    end
+
     return outtxt
   end
 
@@ -316,7 +339,7 @@ class GroupConfig
         group['keys'] = [] unless group['keys'] 
 
         still_contains_groups = false
-        group['members_to_do'].each do |member|
+        group['members_to_do'].clone.each do |member|
           if is_group(member)
             if @groups[member]['members_to_do'].size == 0
               group['keys'].concat @groups[member]['keys']
@@ -358,6 +381,10 @@ class GroupConfig
     end
     return ok, fprs.uniq
   end
+
+  def get_users()
+    return @users
+  end
 end
 
 class EncryptedFile
@@ -402,7 +429,7 @@ class EncryptedFile
     end
     @accessible = true
     @encrypted_content = File.read(filename)
-    (outtxt, stderrtxt, statustxt) = GnuPG.gpgcall(@encrypted_content, %w{--with-colons --no-default-keyring --secret-keyring=/dev/null --keyring=/dev/null})
+    (outtxt, stderrtxt, statustxt) = GnuPG.gpgcall(@encrypted_content, %w{--with-colons --no-options --no-default-keyring --secret-keyring=/dev/null --keyring=/dev/null})
     @encrypted = !(statustxt =~ /\[GNUPG:\] NODATA/)
     if @encrypted
       @readers = EncryptedFile.list_readers(statustxt)
@@ -426,6 +453,8 @@ class EncryptedFile
   def encrypt(content, recipients)
     args = recipients.collect{ |r| "--recipient=#{r}"}
     args.push "--trust-model=always"
+    args.push "--keyring=./.keyring" if FileTest.exists?(".keyring")
+    args.push "--armor"
     args.push "--encrypt"
     (outtxt, stderrtxt, statustxt, exitstatus) = GnuPG.gpgcall(content, args)
 
@@ -588,15 +617,27 @@ class Ed
         proceed = read_input("Warning: Editor did not exit successfully (exit code #{status.exitstatus}.  Proceed?")
         exit(0) unless proceed
       end
-      tempfile.seek(0, IO::SEEK_SET)
-      content = tempfile.read
 
-      # zero the file
+      # some editors do not write new content in place, but instead
+      # make a new file and more it in the old file's place.
+      begin
+        reopened = File.open(tempfile.path, "r+")
+      rescue Exception => e
+        STDERR.puts e
+        exit(1)
+      end
+      content = reopened.read
+
+      # zero the file, well, both of them.
       newsize = content.length
-      tempfile.seek(0, IO::SEEK_SET)
       clearsize = (newsize > oldsize) ? newsize : oldsize
-      tempfile.print "\0"*clearsize
-      tempfile.fsync
+
+      [tempfile, reopened].each do |f|
+        f.seek(0, IO::SEEK_SET)
+        f.print "\0"*clearsize
+        f.fsync
+      end
+      reopened.close
       tempfile.close(true)
 
       if content.length == 0
@@ -624,8 +665,8 @@ class Ed
   def initialize()
     ARGV.options do |opts|
       opts.on_tail("-h", "--help" , "Display this help screen") { help(opts) }
-      opts.on_tail("-n", "--new" , "Edit new file") { |@new| }
-      opts.on_tail("-f", "--force" , "Spawn an editor even if the file is probably not readable") { |@force| }
+      opts.on_tail("-n", "--new" , "Edit new file") { |new| @new=new }
+      opts.on_tail("-f", "--force" , "Spawn an editor even if the file is probably not readable") { |force| @force=force }
       opts.parse!
     end
     help(ARGV.options, 1, STDERR) if ARGV.length != 1
@@ -657,10 +698,57 @@ class Ed
   end
 end
 
+class KeyringUpdater
+  def help(parser, code=0, io=STDOUT)
+    io.puts "Usage: #{$program_name} update-keyring [<keyserver>]"
+    io.puts parser.summarize
+    io.puts "Updates the local .keyring file"
+    exit(code)
+  end
+
+  def initialize()
+    ARGV.options do |opts|
+      opts.on_tail("-h", "--help" , "Display this help screen") { help(opts) }
+      opts.parse!
+    end
+    help(ARGV.options, 1, STDERR) if ARGV.length > 1
+    keyserver = ARGV.shift
+    keyserver = 'keys.gnupg.net' unless keyserver
+
+    groupconfig = GroupConfig.new
+    users = groupconfig.get_users()
+    args = %w{--with-colons --no-options --no-default-keyring --keyring=./.keyring}
+
+    system('touch', '.keyring')
+    users.each_pair() do |uid, keyid|
+      cmd = args.clone()
+      cmd << "--keyserver=#{keyserver}"
+      cmd << "--recv-keys"
+      cmd << keyid
+      puts "Fetching key for #{uid}"
+      (outtxt, stderrtxt, statustxt) = GnuPG.gpgcall('', cmd)
+      unless (statustxt =~ /^\[GNUPG:\] IMPORT_OK /)
+        STDERR.puts "Warning: did not find IMPORT_OK token in status output"
+        STDERR.puts "gpg exited with exit code #{ecode})"
+        STDERR.puts "Command was gpg #{cmd.join(' ')}"
+        STDERR.puts "stdout was #{outtxt}"
+        STDERR.puts "stderr was #{stderrtxt}"
+        STDERR.puts "statustxt was #{statustxt}"
+      end
+
+      cmd = args.clone()
+      cmd << '--batch' << '--edit' << keyid << 'minimize' << 'save'
+      (outtxt, stderrtxt, statustxt, ecode) = GnuPG.gpgcall('', cmd)
+    end
+
+
+  end
+end
 
 def help(code=0, io=STDOUT)
   io.puts "Usage: #{$program_name} ed"
   io.puts "       #{$program_name} ls"
+  io.puts "       #{$program_name} update-keyring"
   io.puts "       #{$program_name} help"
   io.puts "Call #{$program_name} <command> --help for additional options/parameters"
   exit(code)
@@ -669,12 +757,13 @@ end
 
 def parse_command
   case ARGV.shift
-    when 'ls': Ls.new
-    when 'ed': Ed.new
-    when 'help': 
+    when 'ls' then Ls.new
+    when 'ed' then Ed.new
+    when 'update-keyring' then KeyringUpdater.new
+    when 'help' then
       case ARGV.length
-        when 0: help
-        when 1:
+        when 0 then help
+        when 1 then
           ARGV.push "--help"
           parse_command
         else help(1, STDERR)