Download: rpass.rb
#!/usr/bin/ruby
#title:rpass
#date: 2008-05-19
#author:Aaron Radke
# TODO: add an optional encrypted file to use
require 'rubygems'
require 'crypt/rijndael'
require 'base64'
require 'stringio'
require 'crypt/blowfish'
#require 'highline/import'
require 'highline'
   
      
class RPass
   def initialize(opts={})
      @highline = HighLine.new
      @opts = opts
      @opts[:file] = File.dirname(__FILE__) + "/stored.aes" unless @opts.key?(:file)
      @key = @highline.ask("key for #{@opts[:file]}? ") { |q| q.echo = false}
      @rijndael = Crypt::Rijndael.new("%-32.32s" % @key)
      self.decrypt
   end
      
   def decrypt
      if File.exists?(@opts[:file])
         puts "Decrypting ..."
         #---decrypt
         encryptedString = ""
         File.open(@opts[:file], "r").each{|line|
            encryptedString << line
         }
         # TODO?   Need to add the proper logic if the string did not decrypt
         f = File.open(@opts[:file], "r")
         begin
            @decryptedString = @rijndael.decrypt_string(encryptedString)
         rescue RuntimeError
            puts "Error: Could not decrypt: " + $!
            exit
         end
            
         if decryptedString.class != String
            puts "Error: could not decrypt unkown type"
            exit
         end
            
         #some other key will cause the header to be scrambled
         if decryptedString =~ /^title:rpass/
            @decryptedLines = decryptedString.split(/\n/)[1..-1]
         else
            puts "Error: could not decrypt"
            exit
         end
            
      else
         key2 = @highline.ask("new file: renter key ? ") { |q| q.echo = false}
         if key2 != @key
            puts "Error: Setting up new key failed"
            exit
         else
            @decryptedLines = []
         end
      end
   end
      
   def encrypt
      puts "Saving encrypted version..."
      plainString = @decryptedLines.join("\n")
      plainString = "title:rpass\n" + plainString
      f = File.open(@opts[:file],"w")
      print @rijndael.encrypt_stream(plainString)
      f.close
   end
      
   def insert(text, index = -1)
      @decryptedLines.insert(index, text)
   end
      
   def replace(text, index = -1)
      @decryptedLines[index] = text
   end
   def delete(index = -1)
      @decryptedLines.delete_at(index)
   end
      
   def grep(pattern)
      @decryptedLines.each_with_index{|line,i|
         if line =~ /#{pattern}/i
            puts "#{i}: #{line}"
         end
      }
   end
      
   def ask
      command = @highline.ask("rpass> ", String)
         
      command.sub!(/^\s+/,'')
      command.sub!(/\s+$/,'')
         
      if command =~ /^h(elp)?$/
         puts "Available commands:"
         puts %{---------
            (h)elp
            (l)ist
            exit
            (s)ave
            reload
            (g)rep pattern
            (d)elete index
            (i)nsert index text
            (a)dd text
            newkey key
            (r)eplace index text
         }.gsub(/\t+/,'')
      elsif command =~ /^(exit|quit)$/
         encrypt
         return false
      elsif command =~ /^s(ave)?$/
         encrypt
      elsif command =~ /^reload$/
         decrypt
      elsif command =~ /^l(ist)?$/
         grep(".?")
      elsif command =~ /^g(rep)?\s+(.*)$/
         grep($2)
      elsif command =~ /^d(elete)?\s+(-?\d+)$/
         delete($2.to_i)
      elsif command =~ /^i(nsert)?\s+(-?\d+)\s+(.*)$/
         insert($3,$2.to_i)
      elsif command =~ /^a(dd)?\s+(.*)$/
         insert($2,-1)
         grep($2)
      elsif command =~ /^r(eplace)?\s+(-?\d+)\s+(.*)$/
         replace($3,$2.to_i)
      elsif command =~ /^newkey$/
         key1 = @highline.ask("Enter a new key? ") { |q| q.echo = false}
         key2 = @highline.ask("Renter new key? ") { |q| q.echo = false}
         if key1 == key2
            puts "Setting up new key..."
            @key = key1
            @rijndael = Crypt::Rijndael.new(@key)
         else
            puts "Error, key not duplicated.  New key not set."
         end
         replace($2,$1.to_i)
      else
         grep(command)
      end
         
      return true
   end
      
end
   
      
if ARGV.size == 1
   rpass = RPass.new(:file => ARGV[0])
else
   rpass = RPass.new
end
   
while rpass.ask
end