diff --git a/bin/mpw b/bin/mpw
index c14c5af..f36bc87 100755
--- a/bin/mpw
+++ b/bin/mpw
@@ -1,7 +1,6 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
-# info: a simple script who manage your passwords
require 'rubygems'
require 'optparse'
@@ -11,7 +10,7 @@ require 'set'
require 'i18n'
require 'mpw/mpw'
require 'mpw/config'
-require 'mpw/ui/cli'
+require 'mpw/cli'
# --------------------------------------------------------- #
# Set local
@@ -77,26 +76,12 @@ OptionParser.new do |opts|
options[:setup] = true
end
- opts.on('-p', '--protocol PROTOCOL', I18n.t('option.protocol')) do |protocol|
- options[:protocol] = protocol
- end
-
opts.on('-e', '--export FILE', I18n.t('option.export')) do |file|
options[:export] = file
- options[:type] = :yaml
- end
-
- opts.on('-t', '--type TYPE', I18n.t('option.type')) do |type|
- options[:type] = type.to_sym
end
opts.on('-i', '--import FILE', I18n.t('option.import')) do |file|
options[:import] = file
- options[:type] = :yaml
- end
-
- opts.on('-f', '--force', I18n.t('option.force')) do
- options[:force] = true
end
opts.on('-N', '--no-sync', I18n.t('option.no_sync')) do
@@ -131,7 +116,6 @@ elsif not config.check_gpg_key?
end
cli.decrypt
-cli.sync(options[:sync])
# Display the item's informations
if not options[:show].nil?
@@ -156,11 +140,11 @@ elsif not options[:add].nil?
# Export
elsif not options[:export].nil?
- cli.export(options[:export], options[:type])
+ cli.export(options[:export])
# Add a new item
elsif not options[:import].nil?
- cli.import(options[:import], options[:type], options[:force])
+ cli.import(options[:import])
# Interactive mode
end
diff --git a/lib/mpw/config.rb b/lib/mpw/config.rb
index d2b02a9..dd797d2 100644
--- a/lib/mpw/config.rb
+++ b/lib/mpw/config.rb
@@ -2,230 +2,158 @@
# author: nishiki
# mail: nishiki@yaegashi.fr
-require 'rubygems'
require 'gpgme'
require 'yaml'
require 'i18n'
module MPW
- class Config
-
- attr_accessor :error_msg
+class Config
- attr_accessor :key
- attr_accessor :share_keys
- attr_accessor :lang
- attr_accessor :file_gpg
- attr_accessor :last_update
- attr_accessor :sync_type
- attr_accessor :sync_host
- attr_accessor :sync_port
- attr_accessor :sync_user
- attr_accessor :sync_pwd
- attr_accessor :sync_path
- attr_accessor :last_sync
- attr_accessor :dir_config
-
- # Constructor
- # @args: file_config -> the specify config file
- def initialize(file_config=nil)
- @error_msg = nil
+ attr_accessor :error_msg
- if /darwin/ =~ RUBY_PLATFORM
- @dir_config = "#{Dir.home}/Library/Preferences/mpw"
- elsif /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
- @dir_config = "#{Dir.home}/AppData/Local/mpw"
- else
- @dir_config = "#{Dir.home}/.config/mpw"
- end
-
- @file_config = "#{@dir_config}/conf/default.cfg"
- if not file_config.nil? and not file_config.empty?
- @file_config = file_config
- end
- end
-
- # Create a new config file
- # @args: key -> the gpg key to encrypt
- # share_keys -> multiple keys to share the password with other people
- # lang -> the software language
- # file_gpg -> the file who is encrypted
- # sync_type -> the type to synchronization
- # sync_host -> the server host for synchronization
- # sync_port -> the server port for synchronization
- # sync_user -> the user for synchronization
- # sync_pwd -> the password for synchronization
- # sync_suffix -> the suffix file (optionnal)
- # @rtrn: true if le config file is create
- def setup(key, share_keys, lang, file_gpg, sync_type, sync_host, sync_port, sync_user, sync_pwd, sync_path)
-
- if not key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
- @error_msg = I18n.t('error.config.key_bad_format')
- return false
- end
+ attr_accessor :key
+ attr_accessor :lang
+ attr_accessor :config_dir
+ attr_accessor :wallet_dir
- if not check_public_gpg_key(share_keys)
- return false
- end
-
- if file_gpg.empty?
- file_gpg = "#{@dir_config}/db/default.gpg"
- end
-
- config = {'config' => {'key' => key,
- 'share_keys' => share_keys,
- 'lang' => lang,
- 'file_gpg' => file_gpg,
- 'sync_type' => sync_type,
- 'sync_host' => sync_host,
- 'sync_port' => sync_port,
- 'sync_user' => sync_user,
- 'sync_pwd' => sync_pwd,
- 'sync_path' => sync_path,
- 'last_sync' => 0
- }
- }
-
- Dir.mkdir("#{@config_dir}/conf", 700)
- Dir.mkdir("#{@config_dir}/db", 700)
- File.open(@file_config, 'w') do |file|
- file << config.to_yaml
- end
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.config.write')}\n#{e}"
- return false
- end
+ # Constructor
+ # @args: config_file -> the specify config file
+ def initialize(config_file=nil)
+ @error_msg = nil
+ @config_file = config_file
- # Setup a new gpg key
- # @args: password -> the GPG key password
- # name -> the name of user
- # length -> length of the GPG key
- # expire -> the time of expire to GPG key
- # @rtrn: true if the GPG key is create, else false
- def setup_gpg_key(password, name, length = 2048, expire = 0)
- if name.nil? or name.empty?
- @error_msg = "#{I18n.t('error.config.genkey_gpg.name')}"
- return false
- elsif password.nil? or password.empty?
- @error_msg = "#{I18n.t('error.config.genkey_gpg.password')}"
- return false
- end
-
- param = ''
- param << '' + "\n"
- param << "Key-Type: DSA\n"
- param << "Key-Length: #{length}\n"
- param << "Subkey-Type: ELG-E\n"
- param << "Subkey-Length: #{length}\n"
- param << "Name-Real: #{name}\n"
- param << "Name-Comment: #{name}\n"
- param << "Name-Email: #{@key}\n"
- param << "Expire-Date: #{expire}\n"
- param << "Passphrase: #{password}\n"
- param << "\n"
-
- ctx = GPGME::Ctx.new
- ctx.genkey(param, nil, nil)
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}"
- return false
- end
-
- # Check the config file
- # @rtrn: true if the config file is correct
- def checkconfig
- config = YAML::load_file(@file_config)
- @key = config['config']['key']
- @share_keys = config['config']['share_keys']
- @lang = config['config']['lang']
- @file_gpg = config['config']['file_gpg']
- @sync_type = config['config']['sync_type']
- @sync_host = config['config']['sync_host']
- @sync_port = config['config']['sync_port']
- @sync_user = config['config']['sync_user']
- @sync_pwd = config['config']['sync_pwd']
- @sync_path = config['config']['sync_path']
- @last_sync = config['config']['last_sync'].to_i
-
- if @key.empty? or @file_gpg.empty?
- @error_msg = I18n.t('error.config.check')
- return false
- end
- I18n.locale = @lang.to_sym
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.config.check')}\n#{e}"
- return false
- end
-
- # Check if private key exist
- # @rtrn: true if the key exist, else false
- def check_gpg_key?
- ctx = GPGME::Ctx.new
- ctx.each_key(@key, true) do
- return true
- end
-
- return false
- end
-
- # Check if private key exist
- # @args: share_keys -> string with all public keys
- # @rtrn: true if the key exist, else false
- def check_public_gpg_key(share_keys = @share_keys)
- ctx = GPGME::Ctx.new
-
- share_keys = share_keys.nil? ? '' : share_keys
- if not share_keys.empty?
- share_keys.split.each do |k|
- if not k =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
- @error_msg = I18n.t('error.config.key_bad_format')
- return false
- end
-
- ctx.each_key(key, false) do
- next
- end
-
- @error_msg = I18n.t('error.config.no_key_public', key: k)
- return false
- end
- end
-
- return true
- end
-
- # Set the last update when there is a sync
- # @rtrn: true is the file has been updated
- def set_last_sync
- config = {'config' => {'key' => @key,
- 'share_keys' => @share_keys,
- 'lang' => @lang,
- 'file_gpg' => @file_gpg,
- 'sync_type' => @sync_type,
- 'sync_host' => @sync_host,
- 'sync_port' => @sync_port,
- 'sync_user' => @sync_user,
- 'sync_pwd' => @sync_pwd,
- 'sync_path' => @sync_path,
- 'last_sync' => Time.now.to_i
- }
- }
-
- File.open(@file_config, 'w') do |file|
- file << config.to_yaml
- end
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.config.write')}\n#{e}"
- return false
+ if /darwin/ =~ RUBY_PLATFORM
+ @config_dir = "#{Dir.home}/Library/Preferences/mpw"
+ elsif /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
+ @config_dir = "#{Dir.home}/AppData/Local/mpw"
+ else
+ @config_dir = "#{Dir.home}/.config/mpw"
end
+ if @config_file.nil? or @config_file.empty?
+ @config_file = "#{@config_dir}/mpw.cfg"
+ end
end
+
+ # Create a new config file
+ # @args: key -> the gpg key to encrypt
+ # lang -> the software language
+ # wallet_dir -> the directory where are the wallets password
+ # @rtrn: true if le config file is create
+ def setup(key, lang, wallet_dir)
+
+ if not key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/
+ @error_msg = I18n.t('error.config.key_bad_format')
+ return false
+ end
+
+ if wallet_dir.empty?
+ wallet_dir = "#{@config_dir}/wallets"
+ end
+
+ config = {'config' => {'key' => key,
+ 'lang' => lang,
+ 'wallet_dir' => wallet_dir,
+ }
+ }
+
+ Dir.mkdir(wallet_dir, 0700)
+ File.open(@config_file, 'w') do |file|
+ file << config.to_yaml
+ end
+
+ return true
+ rescue Exception => e
+ @error_msg = "#{I18n.t('error.config.write')}\n#{e}"
+ return false
+ end
+
+ # Setup a new gpg key
+ # @args: password -> the GPG key password
+ # name -> the name of user
+ # length -> length of the GPG key
+ # expire -> the time of expire to GPG key
+ # @rtrn: true if the GPG key is create, else false
+ def setup_gpg_key(password, name, length = 4096, expire = 0)
+ if name.nil? or name.empty?
+ @error_msg = "#{I18n.t('error.config.genkey_gpg.name')}"
+ return false
+ elsif password.nil? or password.empty?
+ @error_msg = "#{I18n.t('error.config.genkey_gpg.password')}"
+ return false
+ end
+
+ param = ''
+ param << '' + "\n"
+ param << "Key-Type: DSA\n"
+ param << "Key-Length: #{length}\n"
+ param << "Subkey-Type: ELG-E\n"
+ param << "Subkey-Length: #{length}\n"
+ param << "Name-Real: #{name}\n"
+ param << "Name-Comment: #{name}\n"
+ param << "Name-Email: #{@key}\n"
+ param << "Expire-Date: #{expire}\n"
+ param << "Passphrase: #{password}\n"
+ param << "\n"
+
+ ctx = GPGME::Ctx.new
+ ctx.genkey(param, nil, nil)
+
+ return true
+ rescue Exception => e
+ @error_msg = "#{I18n.t('error.config.genkey_gpg.exception')}\n#{e}"
+ return false
+ end
+
+ # Check the config file
+ # @rtrn: true if the config file is correct
+ def checkconfig
+ config = YAML::load_file(@config_file)
+ @key = config['config']['key']
+ @lang = config['config']['lang']
+ @wallet_dir = config['config']['wallet_dir']
+
+ if @key.empty? or @wallet_dir.empty?
+ @error_msg = I18n.t('error.config.check')
+ return false
+ end
+ I18n.locale = @lang.to_sym
+
+ return true
+ rescue Exception => e
+ @error_msg = "#{I18n.t('error.config.check')}\n#{e}"
+ return false
+ end
+
+ # Check if private key exist
+ # @rtrn: true if the key exist, else false
+ def check_gpg_key?
+ ctx = GPGME::Ctx.new
+ ctx.each_key(@key, true) do
+ return true
+ end
+
+ return false
+ end
+
+ # Set the last update when there is a sync
+ # @rtrn: true is the file has been updated
+ def set_last_sync
+ config = {'config' => {'key' => @key,
+ 'lang' => @lang,
+ 'wallet_dir' => @wallet_dir,
+ }
+ }
+
+ File.open(@config_file, 'w') do |file|
+ file << config.to_yaml
+ end
+
+ return true
+ rescue Exception => e
+ @error_msg = "#{I18n.t('error.config.write')}\n#{e}"
+ return false
+ end
+
+end
end
diff --git a/lib/mpw/item.rb b/lib/mpw/item.rb
index 666d5b3..660f74a 100644
--- a/lib/mpw/item.rb
+++ b/lib/mpw/item.rb
@@ -2,108 +2,104 @@
# author: nishiki
# mail: nishiki@yaegashi.fr
-require 'rubygems'
require 'i18n'
module MPW
- class Item
+class Item
- attr_accessor :error_msg
+ attr_accessor :error_msg
- attr_accessor :id
- attr_accessor :name
- attr_accessor :group
- attr_accessor :host
- attr_accessor :protocol
- attr_accessor :user
- attr_accessor :password
- attr_accessor :port
- attr_accessor :comment
- attr_accessor :last_edit
- attr_accessor :last_sync
- attr_accessor :created
+ attr_accessor :id
+ attr_accessor :name
+ attr_accessor :group
+ attr_accessor :host
+ attr_accessor :protocol
+ attr_accessor :user
+ attr_accessor :port
+ attr_accessor :comment
+ attr_accessor :last_edit
+ attr_accessor :last_sync
+ attr_accessor :created
- # Constructor
- # Create a new item
- # @args: options -> a hash of parameter
- # raise an error if the hash hasn't the key name
- def initialize(options={})
- if not options.has_key?(:name) or options[:name].to_s.empty?
- @error_msg = I18n.t('error.update.name_empty')
- raise @error_msg
- end
-
- if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty?
- @id = generate_id
- @created = Time.now.to_i
- else
- @id = options[:id]
- @created = options[:created]
- @last_edit = options[:last_edit]
- options[:no_update_last_edit] = true
- end
-
- update(options)
+ # Constructor
+ # Create a new item
+ # @args: options -> a hash of parameter
+ # raise an error if the hash hasn't the key name
+ def initialize(options={})
+ if not options.has_key?(:name) or options[:name].to_s.empty?
+ @error_msg = I18n.t('error.update.name_empty')
+ raise @error_msg
end
- # Update the item
- # @args: options -> a hash of parameter
- # @rtrn: true if the item is update
- def update(options={})
- if options.has_key?(:name) and options[:name].to_s.empty?
- @error_msg = I18n.t('error.update.name_empty')
- return false
- end
-
- @name = options[:name] if options.has_key?(:name)
- @group = options[:group] if options.has_key?(:group)
- @host = options[:host] if options.has_key?(:host)
- @protocol = options[:protocol] if options.has_key?(:protocol)
- @user = options[:user] if options.has_key?(:user)
- @password = options[:password] if options.has_key?(:password)
- @port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty?
- @comment = options[:comment] if options.has_key?(:comment)
- @last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit)
-
- return true
+ if not options.has_key?(:id) or options[:id].to_s.empty? or not options.has_key?(:created) or options[:created].to_s.empty?
+ @id = generate_id
+ @created = Time.now.to_i
+ else
+ @id = options[:id]
+ @created = options[:created]
+ @last_edit = options[:last_edit]
+ options[:no_update_last_edit] = true
end
- # Update last_sync
- def set_last_sync
- @last_sync = Time.now.to_i
- end
+ update(options)
+ end
- # Delete all data
- # @rtrn: true
- def delete
- @id = nil
- @name = nil
- @group = nil
- @host = nil
- @protocol = nil
- @user = nil
- @password = nil
- @port = nil
- @comment = nil
- @created = nil
- @last_edit = nil
- @last_sync = nil
-
- return true
- end
-
- def empty?
- return @name.to_s.empty?
- end
-
- def nil?
+ # Update the item
+ # @args: options -> a hash of parameter
+ # @rtrn: true if the item is update
+ def update(options={})
+ if options.has_key?(:name) and options[:name].to_s.empty?
+ @error_msg = I18n.t('error.update.name_empty')
return false
end
- # Generate an random id
- private
- def generate_id
- return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join
- end
+ @name = options[:name] if options.has_key?(:name)
+ @group = options[:group] if options.has_key?(:group)
+ @host = options[:host] if options.has_key?(:host)
+ @protocol = options[:protocol] if options.has_key?(:protocol)
+ @user = options[:user] if options.has_key?(:user)
+ @port = options[:port].to_i if options.has_key?(:port) and not options[:port].to_s.empty?
+ @comment = options[:comment] if options.has_key?(:comment)
+ @last_edit = Time.now.to_i if not options.has_key?(:no_update_last_edit)
+
+ return true
end
+
+ # Update last_sync
+ def set_last_sync
+ @last_sync = Time.now.to_i
+ end
+
+ # Delete all data
+ # @rtrn: true
+ def delete
+ @id = nil
+ @name = nil
+ @group = nil
+ @host = nil
+ @protocol = nil
+ @user = nil
+ @port = nil
+ @comment = nil
+ @created = nil
+ @last_edit = nil
+ @last_sync = nil
+
+ return true
+ end
+
+ def empty?
+ return @name.to_s.empty?
+ end
+
+ def nil?
+ return false
+ end
+
+ # Generate an random id
+ private
+ def generate_id
+ return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(16).join
+ end
+end
end
diff --git a/lib/mpw/mpw.rb b/lib/mpw/mpw.rb
index 57a6bce..9fad675 100644
--- a/lib/mpw/mpw.rb
+++ b/lib/mpw/mpw.rb
@@ -2,331 +2,288 @@
# author: nishiki
# mail: nishiki@yaegashi.fr
-require 'rubygems'
+require 'rubygems/package'
require 'gpgme'
-require 'csv'
require 'i18n'
require 'fileutils'
require 'yaml'
require 'mpw/item'
module MPW
- class MPW
-
- attr_accessor :error_msg
-
- # Constructor
- def initialize(file_gpg, key, share_keys='')
- @error_msg = nil
- @file_gpg = file_gpg
- @key = key
- @share_keys = share_keys
- @data = []
- end
-
- # Decrypt a gpg file
- # @args: password -> the GPG key password
- # @rtrn: true if data has been decrypted
- def decrypt(password=nil)
- @data = []
+class MPW
- if File.exist?(@file_gpg) and not File.zero?(@file_gpg)
- crypto = GPGME::Crypto.new(armor: true)
- data_decrypt = crypto.decrypt(IO.read(@file_gpg), password: password).read.force_encoding('utf-8')
+ attr_accessor :error_msg
+
+ # Constructor
+ def initialize(key, wallet_file, gpg_pass=nil)
+ @error_msg = nil
+ @key = key
+ @gpg_pass = gpg_pass
+ @wallet_file = wallet_file
+ end
- if not data_decrypt.to_s.empty?
- YAML.load(data_decrypt).each_value do |d|
- @data.push(Item.new(id: d['id'],
- name: d['name'],
- group: d['group'],
- host: d['host'],
- protocol: d['protocol'],
- user: d['user'],
- password: d['password'],
- port: d['port'],
- comment: d['comment'],
- last_edit: d['last_edit'],
- created: d['created'],
- )
- )
- end
+ # Decrypt a gpg file
+ # @args: password -> the GPG key password
+ # @rtrn: true if data has been decrypted
+ def read_data
+ @config = nil
+ @keys = []
+ @data = []
+ @passwords = {}
+
+ data = nil
+
+ return if not File.exists?(@wallet_file)
+
+ Gem::Package::TarReader.new(File.open(@wallet_file)) do |tar|
+ tar.each do |f|
+ case f.full_name
+ when 'wallet/config.yml'
+ @config = YAML.load(f.read)
+ check_config
+
+ when 'wallet/meta.gpg'
+ data = decrypt(f.read)
+
+ when /^wallet\/keys\/(?.+)\.pub$/
+ @keys[match['key']] = f.read
+
+ when /^wallet\/passwords\/(?[a-zA-Z0-9]+)\.gpg$/
+ @passwords[Regexp.last_match('id')] = f.read
+ else
+ next
end
end
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
- return false
- end
-
- # Encrypt a file
- # @rtrn: true if the file has been encrypted
- def encrypt
- FileUtils.cp(@file_gpg, "#{@file_gpg}.bk") if File.exist?(@file_gpg)
-
- data_to_encrypt = {}
-
- @data.each do |item|
- next if item.empty?
-
- data_to_encrypt.merge!(item.id => {'id' => item.id,
- 'name' => item.name,
- 'group' => item.group,
- 'host' => item.host,
- 'protocol' => item.protocol,
- 'user' => item.user,
- 'password' => item.password,
- 'port' => item.port,
- 'comment' => item.comment,
- 'last_edit' => item.last_edit,
- 'created' => item.created,
- }
- )
- end
-
- recipients = []
- recipients.push(@key)
- if not @share_keys.nil?
- @share_keys.split.each { |k| recipients.push(k) }
- end
-
- crypto = GPGME::Crypto.new(armor: true)
- file_gpg = File.open(@file_gpg, 'w+')
- crypto.encrypt(data_to_encrypt.to_yaml, recipients: recipients, output: file_gpg)
- file_gpg.close
-
- FileUtils.rm("#{@file_gpg}.bk") if File.exist?("#{@file_gpg}.bk")
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
- FileUtils.mv("#{@file_gpg}.bk", @file_gpg) if File.exist?("#{@file_gpg}.bk")
- return false
- end
-
- # Add a new item
- # @args: item -> Object MPW::Item
- # @rtrn: true if add item
- def add(item)
- if not item.instance_of?(Item)
- @error_msg = I18n.t('error.bad_class')
- return false
- elsif item.empty?
- @error_msg = I18n.t('error.add.empty')
- return false
- else
- @data.push(item)
- return true
- end
end
- # Search in some csv data
- # @args: options -> a hash with paramaters
- # @rtrn: a list with the resultat of the search
- def list(options={})
- result = []
-
- search = options[:search].to_s.downcase
- group = options[:group].to_s.downcase
- protocol = options[:protocol].to_s.downcase
+ if not data.nil? and not data.empty?
+ YAML.load(data).each_value do |d|
+ @data.push(Item.new(id: d['id'],
+ name: d['name'],
+ group: d['group'],
+ host: d['host'],
+ protocol: d['protocol'],
+ user: d['user'],
+ port: d['port'],
+ comment: d['comment'],
+ last_edit: d['last_edit'],
+ created: d['created'],
+ )
+ )
+ end
+ end
+ end
- @data.each do |item|
- next if item.empty?
+ # Encrypt a file
+ # @rtrn: true if the file has been encrypted
+ # TODO export key pub
+ def write_data
+ data = {}
- next if not group.empty? and not group.eql?(item.group.downcase)
- next if not protocol.empty? and not protocol.eql?(item.protocol.downcase)
-
- name = item.name.to_s.downcase
- host = item.host.to_s.downcase
- comment = item.comment.to_s.downcase
+ @data.each do |item|
+ next if item.empty?
- if not name =~ /^.*#{search}.*$/ and not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
- next
+ data.merge!(item.id => {'id' => item.id,
+ 'name' => item.name,
+ 'group' => item.group,
+ 'host' => item.host,
+ 'protocol' => item.protocol,
+ 'user' => item.user,
+ 'port' => item.port,
+ 'comment' => item.comment,
+ 'last_edit' => item.last_edit,
+ 'created' => item.created,
+ }
+ )
+ end
+
+
+
+ Gem::Package::TarWriter.new(File.open(@wallet_file, 'w+')) do |tar|
+ data_encrypt = encrypt(YAML::dump(data))
+ tar.add_file_simple('wallet/meta.gpg', 0400, data_encrypt.length) do |io|
+ io.write(data_encrypt)
+ end
+
+ @passwords.each do |id, password|
+ tar.add_file_simple("wallet/passwords/#{id}.gpg", 0400, password.length) do |io|
+ io.write(password)
end
-
- result.push(item)
end
-
- return result
- end
-
- # Search in some csv data
- # @args: id -> the id item
- # @rtrn: a row with the result of the search
- def search_by_id(id)
- @data.each do |item|
- return item if item.id == id
- end
-
- return nil
- end
-
- # Export to csv
- # @args: file -> file where you export the data
- # type -> udata type
- # @rtrn: true if export work
- def export(file, type=:yaml)
- case type
- when :csv
- CSV.open(file, 'w', write_headers: true,
- headers: ['name', 'group', 'protocol', 'host', 'user', 'password', 'port', 'comment']) do |csv|
- @data.each do |item|
- csv << [item.name, item.group, item.protocol, item.host, item.user, item.password, item.port, item.comment]
- end
- end
-
- when :yaml
- data = {}
- @data.each do |item|
- data.merge!(item.id => {'id' => item.id,
- 'name' => item.name,
- 'group' => item.group,
- 'host' => item.host,
- 'protocol' => item.protocol,
- 'user' => item.user,
- 'password' => item.password,
- 'port' => item.port,
- 'comment' => item.comment,
- 'last_edit' => item.last_edit,
- 'created' => item.created,
- }
- )
- end
-
- File.open(file, 'w') {|f| f << data.to_yaml}
-
- else
- @error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
- return false
- end
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.export.write', file: file)}\n#{e}"
- return false
- end
-
- # Import to csv
- # @args: file -> path to file import
- # type -> udata type
- # @rtrn: true if the import work
- def import(file, type=:yaml)
- case type
- when :csv
- CSV.foreach(file, {headers: true}) do |row|
- item = Item.new(name: row['name'],
- group: row['group'],
- host: row['host'],
- protocol: row['protocol'],
- user: row['user'],
- password: row['password'],
- port: row['port'],
- comment: row['comment'],
- )
-
- return false if item.empty?
-
- @data.push(item)
- end
-
- when :yaml
- YAML::load_file(file).each_value do |row|
- item = Item.new(name: row['name'],
- group: row['group'],
- host: row['host'],
- protocol: row['protocol'],
- user: row['user'],
- password: row['password'],
- port: row['port'],
- comment: row['comment'],
- )
-
- return false if item.empty?
-
- @data.push(item)
- end
-
- else
- @error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
- return false
- end
-
- return true
- rescue Exception => e
- @error_msg = "#{I18n.t('error.import.read', file: file)}\n#{e}"
- return false
- end
-
- # Return a preview import
- # @args: file -> path to file import
- # @rtrn: a hash with the items to import, if there is an error return false
- def import_preview(file, type=:yaml)
- data = []
-
- case type
- when :csv
- CSV.foreach(file, {headers: true}) do |row|
- item = Item.new(name: row['name'],
- group: row['group'],
- host: row['host'],
- protocol: row['protocol'],
- user: row['user'],
- password: row['password'],
- port: row['port'],
- comment: row['comment'],
- )
-
- return false if item.empty?
-
- data.push(item)
- end
-
- when :yaml
- YAML::load_file(file).each_value do |row|
- item = Item.new(name: row['name'],
- group: row['group'],
- host: row['host'],
- protocol: row['protocol'],
- user: row['user'],
- password: row['password'],
- port: row['port'],
- comment: row['comment'],
- )
-
- return false if item.empty?
-
- data.push(item)
- end
-
- else
- @error_msg = "#{I18n.t('error.export.unknown_type', type: type)}"
- return false
- end
-
- return data
- rescue Exception => e
- @error_msg = "#{I18n.t('error.import.read', file: file)}\n#{e}"
- return false
- end
-
- # Generate a random password
- # @args: length -> the length password
- # @rtrn: a random string
- def self.password(length=8)
- if length.to_i <= 0
- length = 8
- else
- length = length.to_i
- end
-
- result = ''
- while length > 62 do
- result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(62).join
- length -= 62
- end
- result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
-
- return result
end
end
+
+ # TODO comment
+ def get_password(id)
+ return decrypt(@passwords[id])
+ end
+
+ # TODO comment
+ def set_password(id, password)
+ @passwords[id] = encrypt(password)
+ end
+
+ # TODO
+ def check_config
+ if false
+ raise 'ERROR'
+ end
+ end
+
+ # Add a new item
+ # @args: item -> Object MPW::Item
+ # @rtrn: true if add item
+ # TODO add password
+ def add(item)
+ if not item.instance_of?(Item)
+ raise I18n.t('error.bad_class')
+ elsif item.empty?
+ raise I18n.t('error.add.empty')
+ else
+ @data.push(item)
+ end
+ end
+
+
+ # Search in some csv data
+ # @args: options -> a hash with paramaters
+ # @rtrn: a list with the resultat of the search
+ def list(options={})
+ result = []
+
+ search = options[:search].to_s.downcase
+ group = options[:group].to_s.downcase
+ protocol = options[:protocol].to_s.downcase
+
+ @data.each do |item|
+ next if item.empty?
+
+ next if not group.empty? and not group.eql?(item.group.downcase)
+ next if not protocol.empty? and not protocol.eql?(item.protocol.downcase)
+
+ name = item.name.to_s.downcase
+ host = item.host.to_s.downcase
+ comment = item.comment.to_s.downcase
+
+ if not name =~ /^.*#{search}.*$/ and not host =~ /^.*#{search}.*$/ and not comment =~ /^.*#{search}.*$/
+ next
+ end
+
+ result.push(item)
+ end
+
+ return result
+ end
+
+ # Search in some csv data
+ # @args: id -> the id item
+ # @rtrn: a row with the result of the search
+ def search_by_id(id)
+ @data.each do |item|
+ return item if item.id == id
+ end
+
+ return nil
+ end
+
+ # Export to csv
+ # @args: file -> file where you export the data
+ def export(file)
+ data = {}
+ @data.each do |item|
+ data.merge!(item.id => {'id' => item.id,
+ 'name' => item.name,
+ 'group' => item.group,
+ 'host' => item.host,
+ 'protocol' => item.protocol,
+ 'user' => item.user,
+ 'password' => get_password(item.id),
+ 'port' => item.port,
+ 'comment' => item.comment,
+ 'last_edit' => item.last_edit,
+ 'created' => item.created,
+ }
+ )
+ end
+
+ File.open(file, 'w') {|f| f << data.to_yaml}
+ rescue Exception => e
+ raise "#{I18n.t('error.export.write', file: file)}\n#{e}"
+ end
+
+ # Import to yaml
+ # @args: file -> path to file import
+ # TODO raise
+ def import(file)
+ YAML::load_file(file).each_value do |row|
+ item = Item.new(name: row['name'],
+ group: row['group'],
+ host: row['host'],
+ protocol: row['protocol'],
+ user: row['user'],
+ port: row['port'],
+ comment: row['comment'],
+ )
+
+ raise 'Item is empty' if item.empty?
+
+ @data.push(item)
+ set_password(item.id, row['password'])
+ end
+ rescue Exception => e
+ raise "#{I18n.t('error.import.read', file: file)}\n#{e}"
+ end
+
+ # Generate a random password
+ # @args: length -> the length password
+ # @rtrn: a random string
+ def self.password(length=8)
+ if length.to_i <= 0
+ length = 8
+ else
+ length = length.to_i
+ end
+
+ result = ''
+ while length > 62 do
+ result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(62).join
+ length -= 62
+ end
+ result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
+
+ return result
+ end
+
+ # Decrypt a gpg file
+ # @args: password -> the GPG key password
+ # @rtrn: true if data has been decrypted
+ private
+ def decrypt(data)
+ crypto = GPGME::Crypto.new(armor: true)
+
+ return crypto.decrypt(data, password: @gpg_pass).read.force_encoding('utf-8')
+ rescue Exception => e
+ raise "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
+ end
+
+ # Encrypt a file
+ # @rtrn: true if the file has been encrypted
+ private
+ def encrypt(data)
+ recipients = []
+ crypto = GPGME::Crypto.new(armor: true)
+
+# @config['keys'].each do |key|
+# recipients.push(key)
+# end
+
+ recipients.push(@key)
+
+ return crypto.encrypt(data, recipients: recipients).read
+ rescue Exception => e
+ raise "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
+ end
+
+end
end
diff --git a/lib/mpw/ui/cli.rb b/lib/mpw/ui/cli.rb
index 3bf9c51..bb6768e 100644
--- a/lib/mpw/ui/cli.rb
+++ b/lib/mpw/ui/cli.rb
@@ -1,15 +1,11 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
-# info: a simple script who m your passwords
-require 'rubygems'
-require 'highline/import'
-require 'pathname'
require 'readline'
require 'i18n'
require 'colorize'
-require 'mpw/sync'
+require 'highline/import'
require 'mpw/mpw'
require 'mpw/item'
@@ -18,29 +14,10 @@ class Cli
# Constructor
# @args: lang -> the operating system language
# config_file -> a specify config file
+ # TODO
def initialize(config)
@config = config
- end
-
- # Sync the data with the server
- # @args: allow_sync -> allow or disable sync (boolean)
- # @rtnr: true if the synchro is finish
- def sync(allow_sync=nil)
- if not allow_sync.nil?
- @allow_sync = allow_sync
- end
-
- return true if not @allow_sync
-
- @sync = MPW::Sync.new(@config, @mpw, @password)
-
- raise(@sync.error_msg) if not @sync.get_remote
- raise(@sync.error_msg) if not @sync.sync
-
- return true
- rescue Exception => e
- puts "#{I18n.t('display.error')} #7: #{e}".red
- return false
+ @wallet_file = "#{@config.wallet_dir}/test.mpw"
end
# Create a new config file
@@ -48,33 +25,16 @@ class Cli
def setup(lang)
puts I18n.t('form.setup.title')
puts '--------------------'
- language = ask(I18n.t('form.setup.lang', lang: lang)).to_s
- key = ask(I18n.t('form.setup.gpg_key')).to_s
- share_keys = ask(I18n.t('form.setup.share_gpg_keys')).to_s
- file_gpg = ask(I18n.t('form.setup.gpg_file', home: @conf.dir_config)).to_s
- sync_type = ask(I18n.t('form.setup.sync_type')).to_s
+ language = ask(I18n.t('form.setup.lang', lang: lang)).to_s
+ key = ask(I18n.t('form.setup.gpg_key')).to_s
+ wallet_dir = ask(I18n.t('form.setup.wallet_dir')).to_s
- if ['ssh', 'ftp', 'mpw'].include?(sync_type)
- sync_host = ask(I18n.t('form.setup.sync_host')).to_s
- sync_port = ask(I18n.t('form.setup.sync_port')).to_s
- sync_user = ask(I18n.t('form.setup.sync_user')).to_s
- sync_pwd = ask(I18n.t('form.setup.sync_pwd')).to_s
- sync_path = ask(I18n.t('form.setup.sync_path')).to_s
- end
-
if language.nil? or language.empty?
language = lang
end
I18n.locale = language.to_sym
- sync_type = sync_type.nil? or sync_type.empty? ? nil : sync_type
- sync_host = sync_host.nil? or sync_host.empty? ? nil : sync_host
- sync_port = sync_port.nil? or sync_port.empty? ? nil : sync_port.to_i
- sync_user = sync_user.nil? or sync_user.empty? ? nil : sync_user
- sync_pwd = sync_pwd.nil? or sync_pwd.empty? ? nil : sync_pwd
- sync_path = sync_path.nil? or sync_path.empty? ? nil : sync_path
-
- if @config.setup(key, share_keys, language, file_gpg, sync_type, sync_host, sync_port, sync_user, sync_pwd, sync_path)
+ if @config.setup(key, lang, wallet_dir)
puts "#{I18n.t('form.setup.valid')}".green
else
puts "#{I18n.t('display.error')} #8: #{@config.error_msg}".red
@@ -86,7 +46,7 @@ class Cli
exit 2
end
end
-
+
# Setup a new GPG key
def setup_gpg_key
puts I18n.t('form.setup_gpg_key.title')
@@ -124,17 +84,15 @@ class Cli
end
end
+
# Request the GPG password and decrypt the file
def decrypt
if not defined?(@mpw)
- @mpw = MPW::MPW.new(@config.file_gpg, @config.key, @config.share_keys)
+ @password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
+ @mpw = MPW::MPW.new(@config.key, @wallet_file, @password)
end
- @password = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
- if not @mpw.decrypt(@password)
- puts "#{I18n.t('display.error')} #11: #{@mpw.error_msg}".red
- exit 2
- end
+ @mpw.read_data
end
# Display the query's result
@@ -185,7 +143,7 @@ class Cli
print "#{I18n.t('display.login')}: ".cyan
puts item.user
print "#{I18n.t('display.password')}: ".cyan
- puts item.password
+ puts @mpw.get_password(item.id)
print "#{I18n.t('display.port')}: ".cyan
puts item.port
print "#{I18n.t('display.comment')}: ".cyan
@@ -203,21 +161,17 @@ class Cli
options[:host] = ask(I18n.t('form.add.server')).to_s
options[:protocol] = ask(I18n.t('form.add.protocol')).to_s
options[:user] = ask(I18n.t('form.add.login')).to_s
- options[:password] = ask(I18n.t('form.add.password')).to_s
+ password = ask(I18n.t('form.add.password')).to_s
options[:port] = ask(I18n.t('form.add.port')).to_s
options[:comment] = ask(I18n.t('form.add.comment')).to_s
item = MPW::Item.new(options)
- if @mpw.add(item)
- if @mpw.encrypt
- sync
- puts "#{I18n.t('form.add.valid')}".green
- else
- puts "#{I18n.t('display.error')} #12: #{@mpw.error_msg}".red
- end
- else
- puts "#{I18n.t('display.error')} #13: #{item.error_msg}".red
- end
+
+ @mpw.add(item)
+ @mpw.set_password(item.id, password)
+ @mpw.write_data
+
+ puts "#{I18n.t('form.add.valid')}".green
end
# Update an item
@@ -235,22 +189,17 @@ class Cli
options[:host] = ask(I18n.t('form.update.server' , server: item.host)).to_s
options[:protocol] = ask(I18n.t('form.update.protocol', protocol: item.protocol)).to_s
options[:user] = ask(I18n.t('form.update.login' , login: item.user)).to_s
- options[:password] = ask(I18n.t('form.update.password')).to_s
+ password = ask(I18n.t('form.update.password')).to_s
options[:port] = ask(I18n.t('form.update.port' , port: item.port)).to_s
options[:comment] = ask(I18n.t('form.update.comment' , comment: item.comment)).to_s
options.delete_if { |k,v| v.empty? }
- if item.update(options)
- if @mpw.encrypt
- sync
- puts "#{I18n.t('form.update.valid')}".green
- else
- puts "#{I18n.t('display.error')} #14: #{@mpw.error_msg}".red
- end
- else
- puts "#{I18n.t('display.error')} #15: #{item.error_msg}".red
- end
+ item.update(options)
+ @mpw.encrypt
+ @mpw.write_data
+
+ puts "#{I18n.t('form.update.valid')}".green
else
puts I18n.t('display.nothing')
end
@@ -289,43 +238,22 @@ class Cli
# Export the items in a CSV file
# @args: file -> the destination file
- def export(file, type=:yaml)
- if @mpw.export(file, type)
- puts "#{I18n.t('export.valid', file)}".green
- else
- puts "#{I18n.t('display.error')} #17: #{@mpw.error_msg}".red
- end
+ def export(file)
+ @mpw.export(file)
+
+ puts "#{I18n.t('export.valid', file)}".green
+ rescue Exception => e
+ puts "#{I18n.t('display.error')} #17: #{e}".red
end
- # Import items from a CSV file
+ # Import items from a YAML file
# @args: file -> the import file
- # force -> no resquest a validation
- def import(file, type=:yaml, force=false)
+ def import(file)
+ @mpw.import(file)
+ @mpw.write_data
- if not force
- result = @mpw.import_preview(file, type)
- if result.is_a?(Array) and not result.empty?
- result.each do |r|
- display_item(r)
- end
-
- confirm = ask("#{I18n.t('form.import.ask', file: file)} (y/N) ").to_s
- if confirm =~ /^(y|yes|YES|Yes|Y)$/
- force = true
- end
- else
- puts I18n.t('form.import.not_valid')
- end
- end
-
- if force
- if @mpw.import(file, type) and @mpw.encrypt
- sync
- puts "#{I18n.t('form.import.valid')}".green
- else
- puts "#{I18n.t('display.error')} #18: #{@mpw.error_msg}".red
- end
- end
+ puts "#{I18n.t('form.import.valid')}".green
+ rescue Exception => e
+ puts "#{I18n.t('display.error')} #18: #{e}".red
end
-
end