1
0
Fork 0
mirror of https://github.com/nishiki/manage-password.git synced 2024-11-27 07:33:05 +00:00

add module MPW

This commit is contained in:
nishiki 2014-01-29 20:49:39 +01:00
parent 7a50c4cf1c
commit 96fe71e07d
10 changed files with 642 additions and 632 deletions

140
MPW/Config.rb Normal file
View file

@ -0,0 +1,140 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
module MPW
require 'rubygems'
require 'yaml'
require 'i18n'
class Config
attr_accessor :error_msg
attr_accessor :key
attr_accessor :lang
attr_accessor :file_gpg
attr_accessor :timeout_pwd
attr_accessor :last_update
attr_accessor :sync_host
attr_accessor :sync_port
attr_accessor :sync_pwd
attr_accessor :sync_suffix
attr_accessor :last_update
# Constructor
# @args: file_config -> the specify config file
def initialize(file_config=nil)
@error_msg = nil
@file_config = "#{Dir.home()}/.mpw.cfg"
if !file_config.nil? && !file_config.empty?
@file_config = file_config
end
end
# Create a new config file
# @args: key -> the gpg key to encrypt
# lang -> the software language
# file_gpg -> the file who is encrypted
# timeout_pwd -> time to save the password
# sync_host -> the server host for synchronization
# sync_port -> the server port 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, lang, file_gpg, timeout_pwd, sync_host=nil, sync_port=nil, sync_pwd=nil, sync_suffix=nil)
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 file_gpg.empty?
file_gpg = "#{Dir.home()}/.mpw.gpg"
end
timeout_pwd = timeout_pwd.empty? ? 60 : timeout_pwd.to_i
config = {'config' => {'key' => key,
'lang' => lang,
'file_gpg' => file_gpg,
'timeout_pwd' => timeout_pwd,
'sync_host' => sync_host,
'sync_port' => sync_port,
'sync_pwd' => sync_pwd,
'sync_suffix' => sync_suffix,
'last_update' => 0 }}
begin
File.open(@file_config, 'w') do |file|
file << config.to_yaml
end
rescue Exception => e
@error_msg = "#{I18n.t('error.config.write')}\n#{e}"
return false
end
return true
end
# Check the config file
# @rtrn: true if the config file is correct
def checkconfig()
begin
config = YAML::load_file(@file_config)
@key = config['config']['key']
@lang = config['config']['lang']
@file_gpg = config['config']['file_gpg']
@timeout_pwd = config['config']['timeout_pwd'].to_i
@sync_host = config['config']['sync_host']
@sync_port = config['config']['sync_port']
@sync_pwd = config['config']['sync_pwd']
@sync_suffix = config['config']['sync_suffix']
@last_update = config['config']['last_update'].to_i
if @key.empty? || @file_gpg.empty?
@error_msg = I18n.t('error.config.check')
return false
end
I18n.locale = @lang.to_sym
rescue Exception => e
@error_msg = "#{I18n.t('error.config.check')}\n#{e}"
return false
end
return true
end
# Set the last update when there is a sync
# @rtrn: true is the file has been updated
def set_last_update()
config = {'config' => {'key' => @key,
'lang' => @lang,
'file_gpg' => @file_gpg,
'timeout_pwd' => @timeout_pwd,
'sync_host' => @sync_host,
'sync_port' => @sync_port,
'sync_pwd' => @sync_pwd,
'sync_suffix' => @sync_suffix,
'last_update' => Time.now.to_i }}
begin
File.open(@file_config, 'w') do |file|
file << config.to_yaml
end
rescue Exception => e
@error_msg = "#{I18n.t('error.config.write')}\n#{e}"
return false
end
return true
end
end
end

335
MPW/MPW.rb Normal file
View file

@ -0,0 +1,335 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
module MPW
require 'rubygems'
require 'gpgme'
require 'csv'
require 'i18n'
class MPW
ID = 0
NAME = 1
GROUP = 2
PROTOCOL = 3
SERVER = 4
LOGIN = 5
PASSWORD = 6
PORT = 7
COMMENT = 8
DATE = 9
attr_accessor :error_msg
# Constructor
def initialize(file_gpg, key=nil)
@error_msg = nil
@file_gpg = file_gpg
@key = key
end
# Decrypt a gpg file
# @args: password -> the GPG key password
# @rtrn: true if data has been decrypted
def decrypt(passwd=nil)
@data = Array.new
begin
if File.exist?(@file_gpg)
crypto = GPGME::Crypto.new(:armor => true)
data_decrypt = crypto.decrypt(IO.read(@file_gpg), :password => passwd).read
data_decrypt.lines do |line|
@data.push(line.parse_csv)
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
return false
end
end
# Encrypt a file
# @rtrn: true if the file has been encrypted
def encrypt()
begin
crypto = GPGME::Crypto.new(:armor => true)
file_gpg = File.open(@file_gpg, 'w+')
data_to_encrypt = ''
@data.each do |row|
data_to_encrypt << row.to_csv
end
crypto.encrypt(data_to_encrypt, :recipients => @key, :output => file_gpg)
file_gpg.close
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
return false
end
end
# Search in some csv data
# @args: search -> the string to search
# protocol -> the connection protocol (ssh, web, other)
# @rtrn: a list with the resultat of the search
def search(search='', group=nil, protocol=nil)
result = Array.new()
if !search.nil?
search = search.downcase
end
search = search.force_encoding('ASCII-8BIT')
@data.each do |row|
name = row[NAME].nil? ? nil : row[NAME].downcase
server = row[SERVER].nil? ? nil : row[SERVER].downcase
comment = row[COMMENT].nil? ? nil : row[COMMENT].downcase
if name =~ /^.*#{search}.*$/ || server =~ /^.*#{search}.*$/ || comment =~ /^.*#{search}.*$/
if (protocol.nil? || protocol.eql?(row[PROTOCOL])) && (group.nil? || group.eql?(row[GROUP]))
result.push(row)
end
end
end
return result
end
# Search in some csv data
# @args: id -> the id item
# @rtrn: a row with the resultat of the search
def search_by_id(id)
@data.each do |row|
if row[ID] == id
return row
end
end
return Array.new()
end
# Update an item
# @args: id -> the item's identifiant
# name -> the item name
# group -> the item group
# server -> the ip or hostname
# protocol -> the protocol
# login -> the login
# passwd -> the password
# port -> the port
# comment -> a comment
# @rtrn: true if the item has been updated
def update(name, group, server, protocol, login, passwd, port, comment, id=nil)
row = Array.new()
update = false
i = 0
@data.each do |r|
if r[ID] == id
row = r
update = true
break
end
i += 1
end
if port.to_i <= 0
port = nil
end
row_update = Array.new()
row_update[DATE] = Time.now.to_i
row_update[ID] = id.nil? || id.empty? ? MPW.password(16) : id
row_update[NAME] = name.nil? || name.empty? ? row[NAME] : name
row_update[GROUP] = group.nil? || group.empty? ? row[GROUP] : group
row_update[SERVER] = server.nil? || server.empty? ? row[SERVER] : server
row_update[PROTOCOL] = protocol.nil? || protocol.empty? ? row[PROTOCOL] : protocol
row_update[LOGIN] = login.nil? || login.empty? ? row[LOGIN] : login
row_update[PASSWORD] = passwd.nil? || passwd.empty? ? row[PASSWORD] : passwd
row_update[PORT] = port.nil? || port.empty? ? row[PORT] : port
row_update[COMMENT] = comment.nil? || comment.empty? ? row[COMMENT] : comment
row_update[] = row_update[NAME].nil? ? nil : row_update[NAME].force_encoding('ASCII-8BIT')
row_update[] = row_update[GROUP].nil? ? nil : row_update[GROUP].force_encoding('ASCII-8BIT')
row_update[] = row_update[SERVER].nil? ? nil : row_update[SERVER].force_encoding('ASCII-8BIT')
row_update[] = row_update[PROTOCOL].nil? ? nil : row_update[PROTOCOL].force_encoding('ASCII-8BIT')
row_update[] = row_update[LOGIN].nil? ? nil : row_update[LOGIN].force_encoding('ASCII-8BIT')
row_update[] = row_update[PASSWORD].nil? ? nil : row_update[PASSWORD].force_encoding('ASCII-8BIT')
row_update[] = row_update[COMMENT].nil? ? nil : row_update[COMMENT].force_encoding('ASCII-8BIT')
if row_update[NAME].nil? || row_update[NAME].empty?
@error_msg = I18n.t('error.update.name_empty')
return false
end
if update
@data[i] = row_update
else
@data.push(row_update)
end
return true
end
# Remove an item
# @args: id -> the item's identifiant
# @rtrn: true if the item has been deleted
def remove(id)
i = 0
@data.each do |row|
if row[ID] == id
@data.delete_at(i)
return true
end
i += 1
end
@error_msg = I18n.t('error.delete.id_no_exist', :id => id)
return false
end
# Export to csv
# @args: file -> a string to match
# @rtrn: true if export work
def export(file)
begin
File.open(file, 'w+') do |file|
@data.each do |row|
row.delete_at(ID).delete_at(DATE)
file << row.to_csv
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.export.write', :file => file)}\n#{e}"
return false
end
end
# Import to csv
# @args: file -> path to file import
# @rtrn: true if the import work
def import(file)
begin
data_new = IO.read(file)
data_new.lines do |line|
if not line =~ /(.*,){6}/
@error_msg = I18n.t('error.import.bad_format')
return false
else
row = line.parse_csv.unshift(0)
if not update(row[NAME], row[GROUP], row[SERVER], row[PROTOCOL], row[LOGIN], row[PASSWORD], row[PORT], row[COMMENT])
return false
end
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.import.read', :file => file)}\n#{e}"
return false
end
end
# Return a preview import
# @args: file -> path to file import
# @rtrn: an array with the items to import, if there is an error return false
def import_preview(file)
begin
result = Array.new()
id = 0
data = IO.read(file)
data.lines do |line|
if not line =~ /(.*,){6}/
@error_msg = I18n.t('error.import.bad_format')
return false
else
result.push(line.parse_csv.unshift(id))
end
id += 1
end
return result
rescue Exception => e
@error_msg = "#{I18n.t('error.import.read', :file => file)}\n#{e}"
return false
end
end
# Sync remote data and local data
# @args: data_remote -> array with the data remote
# last_update -> last update
# @rtrn: false if data_remote is nil
def sync(data_remote, last_update)
if !data_remote.instance_of?(Array)
return false
end
@data.each do |l|
j = 0
update = false
# Update item
data_remote.each do |r|
if l[ID] == r[ID]
if l[DATE].to_i < r[DATE].to_i
update(r[NAME], r[GROUP], r[SERVER], r[PROTOCOL], r[LOGIN], r[PASSWORD], r[PORT], r[COMMENT], l[ID])
end
update = true
data_remote.delete_at(j)
break
end
j += 1
end
# Delete an old item
if !update && l[DATE].to_i < last_update
remove(l[ID])
end
end
# Add item
data_remote.each do |r|
if r[DATE].to_i > last_update
update(r[NAME], r[GROUP], r[SERVER], r[PROTOCOL], r[LOGIN], r[PASSWORD], r[PORT], r[COMMENT], r[ID])
end
end
return encrypt()
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
end

130
MPW/Sync/MPW.rb Normal file
View file

@ -0,0 +1,130 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
module MPW
module Sync
require 'rubygems'
require 'i18n'
require 'socket'
require 'json'
class MPW
attr_accessor :error_msg
attr_accessor :enable
# Constructor
def initialize()
@error_msg = nil
@enable = false
end
# Connect to server
# @args: host -> the server host
# port -> ther connection port
# gpg_key -> the gpg key
# password -> the remote password
# suffix -> the suffix file
# @rtrn: false if the connection fail
def connect(host, port, gpg_key, password, suffix=nil)
@gpg_key = gpg_key
@password = password
@suffix = suffix
begin
@socket = TCPSocket.new(host, port)
@enable = true
rescue Exception => e
@error_msg = "#{I18n.t('error.sync.connection')}\n#{e}"
@enable = false
end
return @enable
end
# Get data on server
# @args: gpg_password -> the gpg password
# @rtrn: nil if nothing data or error
def get(gpg_password)
if !@enable
return nil
end
send_msg = {:action => 'get',
:gpg_key => @gpg_key,
:password => @password,
:suffix => @suffix}
@socket.puts send_msg.to_json
msg = JSON.parse(@socket.gets)
if !defined?(msg['error'])
@error_msg = I18n.t('error.sync.communication')
return nil
elsif msg['error'].nil?
tmp_file = "/tmp/mpw-#{MPW.password()}.gpg"
File.open(tmp_file, 'w') do |file|
file << msg['data']
end
@mpw = MPW.new(tmp_file)
if !@mpw.decrypt(gpg_password)
puts @mpw.error_msg
return nil
end
File.unlink(tmp_file)
return @mpw.search()
else
@error_msg = I18n.t(msg['error'])
return nil
end
end
# Update the remote data
# @args: data -> the data to send on server
# @rtrn: false if there is a problem
def update(data)
if !@enable
return true
end
send_msg = {:action => 'update',
:gpg_key => @gpg_key,
:password => @password,
:suffix => @suffix,
:data => data}
@socket.puts send_msg.to_json
msg = JSON.parse(@socket.gets)
if !defined?(msg['error'])
@error_msg = I18n.t('error.sync.communication')
return false
elsif msg['error'].nil?
return true
else
@error_msg = I18n.t(msg['error'])
return false
end
end
# Close the connection
def close()
if !@enable
return
end
send_msg = {:action => 'close'}
@socket.puts send_msg.to_json
end
end
end
end

View file

@ -10,9 +10,8 @@ require 'readline'
require 'i18n' require 'i18n'
require 'yaml' require 'yaml'
require "#{APP_ROOT}/lib/MPW.rb" require "#{APP_ROOT}/MPW/MPW"
require "#{APP_ROOT}/lib/MPWConfig.rb" require "#{APP_ROOT}/MPW/Sync/MPW"
require "#{APP_ROOT}/lib/Sync.rb"
class Cli class Cli
@ -32,7 +31,7 @@ class Cli
# @rtnr: true if the synchro is finish # @rtnr: true if the synchro is finish
def sync() def sync()
if !defined?(@sync) if !defined?(@sync)
@sync = Sync.new() @sync = MPW::Sync::MPW.new
if !@config.sync_host.nil? && !@config.sync_port.nil? if !@config.sync_host.nil? && !@config.sync_port.nil?
if !@sync.connect(@config.sync_host, @config.sync_port, @config.key, @config.sync_pwd, @config.sync_suffix) if !@sync.connect(@config.sync_host, @config.sync_port, @config.key, @config.sync_pwd, @config.sync_suffix)
@ -99,7 +98,7 @@ class Cli
# Request the GPG password and decrypt the file # Request the GPG password and decrypt the file
def decrypt() def decrypt()
if !defined?(@mpw) if !defined?(@mpw)
@mpw = MPW.new(@config.file_gpg, @config.key) @mpw = MPW::MPW.new(@config.file_gpg, @config.key)
end end
@passwd = ask(I18n.t('display.gpg_password')) {|q| q.echo = false} @passwd = ask(I18n.t('display.gpg_password')) {|q| q.echo = false}
@ -132,30 +131,30 @@ class Cli
# @args: item -> an array with the item information # @args: item -> an array with the item information
def displayFormat(item) def displayFormat(item)
puts '--------------------' puts '--------------------'
puts "Id: #{item[MPW::ID]}" puts "Id: #{item[MPW::MPW::ID]}"
puts "#{I18n.t('display.name')}: #{item[MPW::NAME]}" puts "#{I18n.t('display.name')}: #{item[MPW::MPW::NAME]}"
puts "#{I18n.t('display.group')}: #{item[MPW::GROUP]}" puts "#{I18n.t('display.group')}: #{item[MPW::MPW::GROUP]}"
puts "#{I18n.t('display.server')}: #{item[MPW::SERVER]}" puts "#{I18n.t('display.server')}: #{item[MPW::MPW::SERVER]}"
puts "#{I18n.t('display.protocol')}: #{item[MPW::PROTOCOL]}" puts "#{I18n.t('display.protocol')}: #{item[MPW::MPW::PROTOCOL]}"
puts "#{I18n.t('display.login')}: #{item[MPW::LOGIN]}" puts "#{I18n.t('display.login')}: #{item[MPW::MPW::LOGIN]}"
puts "#{I18n.t('display.password')}: #{item[MPW::PASSWORD]}" puts "#{I18n.t('display.password')}: #{item[MPW::MPW::PASSWORD]}"
puts "#{I18n.t('display.port')}: #{item[MPW::PORT]}" puts "#{I18n.t('display.port')}: #{item[MPW::MPW::PORT]}"
puts "#{I18n.t('display.comment')}: #{item[MPW::COMMENT]}" puts "#{I18n.t('display.comment')}: #{item[MPW::MPW::COMMENT]}"
end end
# Display an item in the alternative format # Display an item in the alternative format
# @args: item -> an array with the item information # @args: item -> an array with the item information
def displayFormatAlt(item) def displayFormatAlt(item)
port = item[MPW::PORT].nil? ? '' : ":#{item[MPW::PORT]}" port = item[MPW::MPW::PORT].nil? ? '' : ":#{item[MPW::MPW::PORT]}"
if item[MPW::PASSWORD].nil? || item[MPW::PASSWORD].empty? if item[MPW::MPW::PASSWORD].nil? || item[MPW::MPW::PASSWORD].empty?
if item[MPW::LOGIN].include('@') if item[MPW::MPW::LOGIN].include('@')
puts "# #{item[MPW::ID]} #{item[MPW::PROTOCOL]}://#{item[MPW::LOGIN]}@#{item[MPW::SERVER]}#{port}" puts "# #{item[MPW::MPW::ID]} #{item[MPW::MPW::PROTOCOL]}://#{item[MPW::MPW::LOGIN]}@#{item[MPW::MPW::SERVER]}#{port}"
else else
puts "# #{item[MPW::ID]} #{item[MPW::PROTOCOL]}://{#{item[MPW::LOGIN]}}@#{item[MPW::SERVER]}#{port}" puts "# #{item[MPW::MPW::ID]} #{item[MPW::MPW::PROTOCOL]}://{#{item[MPW::MPW::LOGIN]}}@#{item[MPW::MPW::SERVER]}#{port}"
end end
else else
puts "# #{item[MPW::ID]} #{item[MPW::PROTOCOL]}://{#{item[MPW::LOGIN]}:#{item[MPW::PASSWORD]}}@#{item[MPW::SERVER]}#{port}" puts "# #{item[MPW::MPW::ID]} #{item[MPW::MPW::PROTOCOL]}://{#{item[MPW::MPW::LOGIN]}:#{item[MPW::MPW::PASSWORD]}}@#{item[MPW::MPW::SERVER]}#{port}"
end end
end end
@ -193,14 +192,14 @@ class Cli
if not row.empty? if not row.empty?
puts I18n.t('form.update.title') puts I18n.t('form.update.title')
puts '--------------------' puts '--------------------'
name = ask(I18n.t('form.update.name' , :name => row[MPW::NAME])).to_s name = ask(I18n.t('form.update.name' , :name => row[MPW::MPW::NAME])).to_s
group = ask(I18n.t('form.update.group' , :group => row[MPW::GROUP])).to_s group = ask(I18n.t('form.update.group' , :group => row[MPW::MPW::GROUP])).to_s
server = ask(I18n.t('form.update.server' , :server => row[MPW::SERVER])).to_s server = ask(I18n.t('form.update.server' , :server => row[MPW::MPW::SERVER])).to_s
protocol = ask(I18n.t('form.update.protocol', :protocol => row[MPW::PROTOCOL])).to_s protocol = ask(I18n.t('form.update.protocol', :protocol => row[MPW::MPW::PROTOCOL])).to_s
login = ask(I18n.t('form.update.login' , :login => row[MPW::LOGIN])).to_s login = ask(I18n.t('form.update.login' , :login => row[MPW::MPW::LOGIN])).to_s
passwd = ask(I18n.t('form.update.password')).to_s passwd = ask(I18n.t('form.update.password')).to_s
port = ask(I18n.t('form.update.port' , :port => row[MPW::PORT])).to_s port = ask(I18n.t('form.update.port' , :port => row[MPW::MPW::PORT])).to_s
comment = ask(I18n.t('form.update.comment' , :comment => row[MPW::COMMENT])).to_s comment = ask(I18n.t('form.update.comment' , :comment => row[MPW::MPW::COMMENT])).to_s
if @mpw.update(name, group, server, protocol, login, passwd, port, comment, id) if @mpw.update(name, group, server, protocol, login, passwd, port, comment, id)
if @mpw.encrypt() if @mpw.encrypt()

View file

@ -3,7 +3,7 @@
# mail: nishiki@yaegashi.fr # mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords # info: a simple script who manage your passwords
require "#{APP_ROOT}/lib/Cli.rb" require "#{APP_ROOT}/MPW/UI/Cli"
class CliSSH < Cli class CliSSH < Cli
@ -16,11 +16,11 @@ class CliSSH < Cli
if result.length > 0 if result.length > 0
result.each do |r| result.each do |r|
server = @server.nil? ? r[MPW::SERVER] : @server server = @server.nil? ? r[MPW::MPW::SERVER] : @server
port = @port.nil? ? r[MPW::PORT] : @port port = @port.nil? ? r[MPW::MPW::PORT] : @port
login = @login.nil? ? r[MPW::LOGIN] : @login login = @login.nil? ? r[MPW::MPW::LOGIN] : @login
passwd = r[MPW::PASSWORD] passwd = r[MPW::MPW::PASSWORD]
if port.nil? || port.empty? if port.nil? || port.empty?
port = 22 port = 22

View file

@ -1,331 +0,0 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
require 'rubygems'
require 'gpgme'
require 'csv'
require 'i18n'
class MPW
ID = 0
NAME = 1
GROUP = 2
PROTOCOL = 3
SERVER = 4
LOGIN = 5
PASSWORD = 6
PORT = 7
COMMENT = 8
DATE = 9
attr_accessor :error_msg
# Constructor
def initialize(file_gpg, key=nil)
@error_msg = nil
@file_gpg = file_gpg
@key = key
end
# Decrypt a gpg file
# @args: password -> the GPG key password
# @rtrn: true if data has been decrypted
def decrypt(passwd=nil)
@data = Array.new
begin
if File.exist?(@file_gpg)
crypto = GPGME::Crypto.new(:armor => true)
data_decrypt = crypto.decrypt(IO.read(@file_gpg), :password => passwd).read
data_decrypt.lines do |line|
@data.push(line.parse_csv)
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.gpg_file.decrypt')}\n#{e}"
return false
end
end
# Encrypt a file
# @rtrn: true if the file has been encrypted
def encrypt()
begin
crypto = GPGME::Crypto.new(:armor => true)
file_gpg = File.open(@file_gpg, 'w+')
data_to_encrypt = ''
@data.each do |row|
data_to_encrypt << row.to_csv
end
crypto.encrypt(data_to_encrypt, :recipients => @key, :output => file_gpg)
file_gpg.close
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.gpg_file.encrypt')}\n#{e}"
return false
end
end
# Search in some csv data
# @args: search -> the string to search
# protocol -> the connection protocol (ssh, web, other)
# @rtrn: a list with the resultat of the search
def search(search='', group=nil, protocol=nil)
result = Array.new()
if !search.nil?
search = search.downcase
end
search = search.force_encoding('ASCII-8BIT')
@data.each do |row|
name = row[NAME].nil? ? nil : row[NAME].downcase
server = row[SERVER].nil? ? nil : row[SERVER].downcase
comment = row[COMMENT].nil? ? nil : row[COMMENT].downcase
if name =~ /^.*#{search}.*$/ || server =~ /^.*#{search}.*$/ || comment =~ /^.*#{search}.*$/
if (protocol.nil? || protocol.eql?(row[PROTOCOL])) && (group.nil? || group.eql?(row[GROUP]))
result.push(row)
end
end
end
return result
end
# Search in some csv data
# @args: id -> the id item
# @rtrn: a row with the resultat of the search
def search_by_id(id)
@data.each do |row|
if row[ID] == id
return row
end
end
return Array.new()
end
# Update an item
# @args: id -> the item's identifiant
# name -> the item name
# group -> the item group
# server -> the ip or hostname
# protocol -> the protocol
# login -> the login
# passwd -> the password
# port -> the port
# comment -> a comment
# @rtrn: true if the item has been updated
def update(name, group, server, protocol, login, passwd, port, comment, id=nil)
row = Array.new()
update = false
i = 0
@data.each do |r|
if r[ID] == id
row = r
update = true
break
end
i += 1
end
if port.to_i <= 0
port = nil
end
row_update = Array.new()
row_update[DATE] = Time.now.to_i
row_update[ID] = id.nil? || id.empty? ? MPW.password(16) : id
row_update[NAME] = name.nil? || name.empty? ? row[NAME] : name
row_update[GROUP] = group.nil? || group.empty? ? row[GROUP] : group
row_update[SERVER] = server.nil? || server.empty? ? row[SERVER] : server
row_update[PROTOCOL] = protocol.nil? || protocol.empty? ? row[PROTOCOL] : protocol
row_update[LOGIN] = login.nil? || login.empty? ? row[LOGIN] : login
row_update[PASSWORD] = passwd.nil? || passwd.empty? ? row[PASSWORD] : passwd
row_update[PORT] = port.nil? || port.empty? ? row[PORT] : port
row_update[COMMENT] = comment.nil? || comment.empty? ? row[COMMENT] : comment
row_update[] = row_update[NAME].nil? ? nil : row_update[NAME].force_encoding('ASCII-8BIT')
row_update[] = row_update[GROUP].nil? ? nil : row_update[GROUP].force_encoding('ASCII-8BIT')
row_update[] = row_update[SERVER].nil? ? nil : row_update[SERVER].force_encoding('ASCII-8BIT')
row_update[] = row_update[PROTOCOL].nil? ? nil : row_update[PROTOCOL].force_encoding('ASCII-8BIT')
row_update[] = row_update[LOGIN].nil? ? nil : row_update[LOGIN].force_encoding('ASCII-8BIT')
row_update[] = row_update[PASSWORD].nil? ? nil : row_update[PASSWORD].force_encoding('ASCII-8BIT')
row_update[] = row_update[COMMENT].nil? ? nil : row_update[COMMENT].force_encoding('ASCII-8BIT')
if row_update[NAME].nil? || row_update[NAME].empty?
@error_msg = I18n.t('error.update.name_empty')
return false
end
if update
@data[i] = row_update
else
@data.push(row_update)
end
return true
end
# Remove an item
# @args: id -> the item's identifiant
# @rtrn: true if the item has been deleted
def remove(id)
i = 0
@data.each do |row|
if row[ID] == id
@data.delete_at(i)
return true
end
i += 1
end
@error_msg = I18n.t('error.delete.id_no_exist', :id => id)
return false
end
# Export to csv
# @args: file -> a string to match
# @rtrn: true if export work
def export(file)
begin
File.open(file, 'w+') do |file|
@data.each do |row|
row.delete_at(ID).delete_at(DATE)
file << row.to_csv
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.export.write', :file => file)}\n#{e}"
return false
end
end
# Import to csv
# @args: file -> path to file import
# @rtrn: true if the import work
def import(file)
begin
data_new = IO.read(file)
data_new.lines do |line|
if not line =~ /(.*,){6}/
@error_msg = I18n.t('error.import.bad_format')
return false
else
row = line.parse_csv.unshift(0)
if not update(row[NAME], row[GROUP], row[SERVER], row[PROTOCOL], row[LOGIN], row[PASSWORD], row[PORT], row[COMMENT])
return false
end
end
end
return true
rescue Exception => e
@error_msg = "#{I18n.t('error.import.read', :file => file)}\n#{e}"
return false
end
end
# Return a preview import
# @args: file -> path to file import
# @rtrn: an array with the items to import, if there is an error return false
def import_preview(file)
begin
result = Array.new()
id = 0
data = IO.read(file)
data.lines do |line|
if not line =~ /(.*,){6}/
@error_msg = I18n.t('error.import.bad_format')
return false
else
result.push(line.parse_csv.unshift(id))
end
id += 1
end
return result
rescue Exception => e
@error_msg = "#{I18n.t('error.import.read', :file => file)}\n#{e}"
return false
end
end
# Sync remote data and local data
# @args: data_remote -> array with the data remote
# last_update -> last update
# @rtrn: false if data_remote is nil
def sync(data_remote, last_update)
if !data_remote.instance_of?(Array)
return false
end
@data.each do |l|
j = 0
update = false
# Update item
data_remote.each do |r|
if l[ID] == r[ID]
if l[DATE].to_i < r[DATE].to_i
update(r[NAME], r[GROUP], r[SERVER], r[PROTOCOL], r[LOGIN], r[PASSWORD], r[PORT], r[COMMENT], l[ID])
end
update = true
data_remote.delete_at(j)
break
end
j += 1
end
# Delete an old item
if !update && l[DATE].to_i < last_update
remove(l[ID])
end
end
# Add item
data_remote.each do |r|
if r[DATE].to_i > last_update
update(r[NAME], r[GROUP], r[SERVER], r[PROTOCOL], r[LOGIN], r[PASSWORD], r[PORT], r[COMMENT], r[ID])
end
end
return encrypt()
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

View file

@ -1,136 +0,0 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
require 'rubygems'
require 'yaml'
require 'i18n'
class MPWConfig
attr_accessor :error_msg
attr_accessor :key
attr_accessor :lang
attr_accessor :file_gpg
attr_accessor :timeout_pwd
attr_accessor :last_update
attr_accessor :sync_host
attr_accessor :sync_port
attr_accessor :sync_pwd
attr_accessor :sync_suffix
attr_accessor :last_update
# Constructor
# @args: file_config -> the specify config file
def initialize(file_config=nil)
@error_msg = nil
@file_config = "#{Dir.home()}/.mpw.cfg"
if !file_config.nil? && !file_config.empty?
@file_config = file_config
end
end
# Create a new config file
# @args: key -> the gpg key to encrypt
# lang -> the software language
# file_gpg -> the file who is encrypted
# timeout_pwd -> time to save the password
# sync_host -> the server host for synchronization
# sync_port -> the server port 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, lang, file_gpg, timeout_pwd, sync_host=nil, sync_port=nil, sync_pwd=nil, sync_suffix=nil)
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 file_gpg.empty?
file_gpg = "#{Dir.home()}/.mpw.gpg"
end
timeout_pwd = timeout_pwd.empty? ? 60 : timeout_pwd.to_i
config = {'config' => {'key' => key,
'lang' => lang,
'file_gpg' => file_gpg,
'timeout_pwd' => timeout_pwd,
'sync_host' => sync_host,
'sync_port' => sync_port,
'sync_pwd' => sync_pwd,
'sync_suffix' => sync_suffix,
'last_update' => 0 }}
begin
File.open(@file_config, 'w') do |file|
file << config.to_yaml
end
rescue Exception => e
@error_msg = "#{I18n.t('error.config.write')}\n#{e}"
return false
end
return true
end
# Check the config file
# @rtrn: true if the config file is correct
def checkconfig()
begin
config = YAML::load_file(@file_config)
@key = config['config']['key']
@lang = config['config']['lang']
@file_gpg = config['config']['file_gpg']
@timeout_pwd = config['config']['timeout_pwd'].to_i
@sync_host = config['config']['sync_host']
@sync_port = config['config']['sync_port']
@sync_pwd = config['config']['sync_pwd']
@sync_suffix = config['config']['sync_suffix']
@last_update = config['config']['last_update'].to_i
if @key.empty? || @file_gpg.empty?
@error_msg = I18n.t('error.config.check')
return false
end
I18n.locale = @lang.to_sym
rescue Exception => e
@error_msg = "#{I18n.t('error.config.check')}\n#{e}"
return false
end
return true
end
# Set the last update when there is a sync
# @rtrn: true is the file has been updated
def set_last_update()
config = {'config' => {'key' => @key,
'lang' => @lang,
'file_gpg' => @file_gpg,
'timeout_pwd' => @timeout_pwd,
'sync_host' => @sync_host,
'sync_port' => @sync_port,
'sync_pwd' => @sync_pwd,
'sync_suffix' => @sync_suffix,
'last_update' => Time.now.to_i }}
begin
File.open(@file_config, 'w') do |file|
file << config.to_yaml
end
rescue Exception => e
@error_msg = "#{I18n.t('error.config.write')}\n#{e}"
return false
end
return true
end
end

View file

@ -1,128 +0,0 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
require 'rubygems'
require 'i18n'
require 'socket'
require 'json'
require "#{APP_ROOT}/lib/MPW.rb"
class Sync
attr_accessor :error_msg
attr_accessor :enable
# Constructor
def initialize()
@error_msg = nil
@enable = false
end
# Connect to server
# @args: host -> the server host
# port -> ther connection port
# gpg_key -> the gpg key
# password -> the remote password
# suffix -> the suffix file
# @rtrn: false if the connection fail
def connect(host, port, gpg_key, password, suffix=nil)
@gpg_key = gpg_key
@password = password
@suffix = suffix
begin
@socket = TCPSocket.new(host, port)
@enable = true
rescue Exception => e
@error_msg = "#{I18n.t('error.sync.connection')}\n#{e}"
@enable = false
end
return @enable
end
# Get data on server
# @args: gpg_password -> the gpg password
# @rtrn: nil if nothing data or error
def get(gpg_password)
if !@enable
return nil
end
send_msg = {:action => 'get',
:gpg_key => @gpg_key,
:password => @password,
:suffix => @suffix}
@socket.puts send_msg.to_json
msg = JSON.parse(@socket.gets)
if !defined?(msg['error'])
@error_msg = I18n.t('error.sync.communication')
return nil
elsif msg['error'].nil?
tmp_file = "/tmp/mpw-#{MPW.password()}.gpg"
File.open(tmp_file, 'w') do |file|
file << msg['data']
end
@mpw = MPW.new(tmp_file)
if !@mpw.decrypt(gpg_password)
puts @mpw.error_msg
return nil
end
File.unlink(tmp_file)
return @mpw.search()
else
@error_msg = I18n.t(msg['error'])
return nil
end
end
# Update the remote data
# @args: data -> the data to send on server
# @rtrn: false if there is a problem
def update(data)
if !@enable
return true
end
send_msg = {:action => 'update',
:gpg_key => @gpg_key,
:password => @password,
:suffix => @suffix,
:data => data}
@socket.puts send_msg.to_json
msg = JSON.parse(@socket.gets)
if !defined?(msg['error'])
@error_msg = I18n.t('error.sync.communication')
return false
elsif msg['error'].nil?
return true
else
@error_msg = I18n.t(msg['error'])
return false
end
end
# TODO
def delete()
end
# Close the connection
def close()
if !@enable
return
end
send_msg = {:action => 'close'}
@socket.puts send_msg.to_json
end
end

6
mpw
View file

@ -10,8 +10,8 @@ require 'locale'
require 'i18n' require 'i18n'
APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath) APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath)
require "#{APP_ROOT}/lib/Cli.rb" require "#{APP_ROOT}/MPW/UI/Cli"
require "#{APP_ROOT}/lib/MPW.rb" require "#{APP_ROOT}/MPW/Config"
lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
@ -93,7 +93,7 @@ OptionParser.new do |opts|
end end
end.parse! end.parse!
config = MPWConfig.new(options[:config]) config = MPW::Config.new(options[:config])
check_error = config.checkconfig() check_error = config.checkconfig()
cli = Cli.new(lang, config) cli = Cli.new(lang, config)

View file

@ -10,7 +10,8 @@ require 'locale'
require 'i18n' require 'i18n'
APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath) APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath)
require "#{APP_ROOT}/lib/CliSSH.rb" require "#{APP_ROOT}/MPW/UI/CliSSH"
require "#{APP_ROOT}/MPW/Config"
lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1] lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
@ -45,7 +46,7 @@ OptionParser.new do |opts|
end end
end.parse! end.parse!
config = MPWConfig.new(options[:config]) config = MPW::Config.new(options[:config])
check_error = config.checkconfig() check_error = config.checkconfig()
cli = CliSSH.new(lang, config) cli = CliSSH.new(lang, config)