1
0
Fork 0
mirror of https://github.com/nishiki/manage-password.git synced 2025-02-17 08:30:04 +00:00

merge to server branch

This commit is contained in:
nishiki 2014-01-15 23:32:00 +01:00
commit a8b4ca29c9
10 changed files with 863 additions and 208 deletions

View file

@ -1 +1 @@
1.0.0 - stable
1.1.0 - dev

View file

@ -1,8 +1,6 @@
---
en:
error:
add:
name_empty: "You must define a name!"
config:
write: "Can't write the config file!"
check: "Checkconfig failed!"
@ -18,7 +16,12 @@ en:
bad_format: "Can't import, the file is badly formated!"
read: "Can't import, unable to read %{file}!"
update:
id_no_exist: "Can't update the item, the item %{id} doesn't exist!"
name_empty: "You must define a name!"
sync:
connection: "Connection fail!"
no_data: "Nothing data!"
not_authorized: "You haven't the access to remote file!"
unknown: "An unknown error is occured!"
cli:
option:
usage: "Usage"
@ -101,6 +104,8 @@ en:
port: "Port"
protocol: "Protocol"
server: "Server"
sync:
not_connect: "The server connection fail!"
ssh:
option:
usage: "Usage"
@ -111,6 +116,25 @@ en:
display:
connect: "Connection to:"
nothing: "Nothing result!"
server:
option:
usage: "Usage"
config: "Specifie the configuration file"
checkconfig: "Check the configuration"
setup: "Setup a new configuration file"
help: "Show this message help"
checkconfig:
fail: "Checkconfig failed:!"
empty: "ERROR: an importe option is missing!"
datadir: "ERROR: le data directory doesn't exist!"
form:
setup:
title: "Serveur configuration"
host: "IP listen: "
port: "Port listen: "
data_dir: "Data directory: "
timeout: "Timeout to second: "
not_valid: "ERROR: Impossible to write the configuration file!"
formats:
default: ! '%Y-%m-%d'
long: ! '%B %d, %Y'

View file

@ -1,8 +1,6 @@
---
fr:
error:
add:
name_empty: "Vous devez définir un nom!"
config:
write: "Impossible d'écrire le fichier de configuration!"
check: "Le fichier de configuration est invalide!"
@ -18,7 +16,12 @@ fr:
bad_format: "Impossible d'importer le fichier car son format est incorrect!"
read: "Impossible d'importer le fichier %{file}, car il n'est pas lisible!"
update:
id_no_exist: "Impossible de mettre à jour l'élément %{id}, car il n'existe pas!"
name_empty: "Vous devez définir un nom!"
sync:
connection: "La connexion n'a pu être établie"
no_data: "Aucune data!"
not_authorized: "Vous n'avez pas les autorisations d'accès au fichier distant!"
unknown: "Une erreur inconnue est survenue!"
cli:
option:
usage: "Utilisation"
@ -101,6 +104,8 @@ fr:
port: "Port"
protocol: "Protocol"
server: "Serveur"
sync:
not_connect: "La connexion au serveur n'a pu être établie!"
ssh:
option:
usage: "Utilisation"
@ -111,6 +116,25 @@ fr:
display:
connect: "Connexion à:"
nothing: "Aucun résultat!"
server:
option:
usage: "Utilisation"
config: "Spécifie le fichier de configuration"
checkconfig: "Vérifie le fichier de configuration"
setup: "Permet de générer un nouveau fichier de configuration"
help: "Affiche ce message d'aide"
checkconfig:
fail: "Le fichier de configuration est invalide!"
empty: "ERREUR: Une option importante est manquante!"
datadir: "ERREUR: Le répertoire des données n'existe pas!"
form:
setup:
title: "Configuration du serveur"
host: "IP d'écoute: "
port: "Port d'écoute: "
data_dir: "Répertoire des données: "
timeout: "Timeout en seconde: "
not_valid: "ERREUR: Impossible d'écire le fichier de configuration!"
formats:
default: ! '%Y-%m-%d'
long: ! '%B %d, %Y'

View file

@ -8,24 +8,47 @@ require 'highline/import'
require 'pathname'
require 'readline'
require 'i18n'
require 'yaml'
require "#{APP_ROOT}/lib/MPW.rb"
require "#{APP_ROOT}/lib/MPWConfig.rb"
require "#{APP_ROOT}/lib/Sync.rb"
class Cli
# Constructor
# @args: lang -> the operating system language
# config_file -> a specify config file
def initialize(lang, config_file=nil)
@m = MPW.new(config_file)
if not @m.checkconfig()
self.setup(lang)
def initialize(lang, config)
@config = config
@mpw = MPW.new(@config.file_gpg, @config.key)
if not decrypt()
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
exit 2
end
if not self.decrypt()
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
exit 2
@sync = Sync.new()
if @config.sync_host.nil? || @config.sync_port.nil?
@sync.disable()
elsif !@sync.connect(@config.sync_host, @config.sync_port, @config.key, @config.sync_pwd, @config.sync_suffix)
puts "#{I18n.t('cli.sync.not_connect')}:\n#{@sync.error_msg}"
end
end
# Destructor
def finalize()
@sync.close()
end
# Sync the data with the server
def sync()
begin
@mpw.sync(@sync.get(@passwd), @config.last_update)
@sync.update(File.open(@config.file_gpg).read)
@config.setLastUpdate()
rescue Exception => e
puts "#{I18n.t('cli.sync.error')}:\n#{e}"
end
end
@ -44,35 +67,35 @@ class Cli
end
I18n.locale = language.to_sym
if @m.setup(key, language, file_gpg, timeout_pwd)
if @config.setup(key, language, file_gpg, timeout_pwd)
puts I18n.t('cli.form.setup.valid')
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@config.error_msg}"
end
if not @m.checkconfig()
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
if not @config.checkconfig()
puts "#{I18n.t('cli.display.error')}: #{@config.error_msg}"
end
end
# Request the GPG password and decrypt the file
def decrypt()
@passwd = ask(I18n.t('cli.display.gpg_password')) {|q| q.echo = false}
return @m.decrypt(@passwd)
return @mpw.decrypt(@passwd)
end
# Display the query's result
# @args: search -> the string to search
# protocol -> search from a particular protocol
def display(search, protocol=nil, group=nil, format=nil)
result = @m.search(search, group, protocol)
result = @mpw.search(search, group, protocol)
if not result.empty?
result.each do |r|
if format.nil? || !format
self.displayFormat(r)
displayFormat(r)
else
self.displayFormatAlt(r)
displayFormatAlt(r)
end
end
else
@ -125,21 +148,21 @@ class Cli
port = ask(I18n.t('cli.form.add.port')).to_s
comment = ask(I18n.t('cli.form.add.comment')).to_s
if @m.add(name, group, server, protocol, login, passwd, port, comment)
if @m.encrypt()
if @mpw.update(name, group, server, protocol, login, passwd, port, comment)
if @mpw.encrypt()
puts I18n.t('cli.form.add.valid')
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
end
# Update an item
# @args: id -> the item's id
def update(id)
row = @m.searchById(id)
row = @mpw.searchById(id)
if not row.empty?
puts I18n.t('cli.form.update.title')
@ -153,14 +176,14 @@ class Cli
port = ask(I18n.t('cli.form.update.port' , :port => row[MPW::PORT])).to_s
comment = ask(I18n.t('cli.form.update.comment' , :comment => row[MPW::COMMENT])).to_s
if @m.update(id, name, group, server, protocol, login, passwd, port, comment)
if @m.encrypt()
if @mpw.update(name, group, server, protocol, login, passwd, port, comment, id)
if @mpw.encrypt()
puts I18n.t('cli.form.update.valid')
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
else
puts I18n.t('cli.display.nothing')
@ -172,10 +195,10 @@ class Cli
# force -> no resquest a validation
def remove(id, force=false)
if not force
result = @m.searchById(id)
result = @mpw.searchById(id)
if result.length > 0
self.displayFormat(result)
displayFormat(result)
confirm = ask("#{I18n.t('cli.form.delete.ask', :id => id)} (y/N) ").to_s
if confirm =~ /^(y|yes|YES|Yes|Y)$/
@ -187,11 +210,11 @@ class Cli
end
if force
if @m.remove(id)
if @m.encrypt()
if @mpw.remove(id)
if @mpw.encrypt()
puts I18n.t('cli.form.delete.valid', :id => id)
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
else
puts I18n.t('cli.form.delete.not_valid')
@ -202,10 +225,10 @@ class Cli
# Export the items in a CSV file
# @args: file -> the destination file
def export(file)
if @m.export(file)
if @mpw.export(file)
puts "The export in #{file} is succesfull!"
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
end
@ -214,12 +237,12 @@ class Cli
# @args: file -> the import file
# force -> no resquest a validation
def import(file, force=false)
result = @m.importPreview(file)
result = @mpw.importPreview(file)
if not force
if result.is_a?(Array) && !result.empty?
result.each do |r|
self.displayFormat(r)
displayFormat(r)
end
confirm = ask("#{I18n.t('cli.form.import.ask', :file => file)} (y/N) ").to_s
@ -232,10 +255,10 @@ class Cli
end
if force
if @m.import(file) && @m.encrypt()
if @mpw.import(file) && @mpw.encrypt()
puts I18n.t('cli.form.import.valid')
else
puts "#{I18n.t('cli.display.error')}: #{@m.error_msg}"
puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}"
end
end
end
@ -247,7 +270,7 @@ class Cli
while buf = Readline.readline('<mpw> ', true)
if @m.timeout_pwd < Time.now.to_i - last_access
if @config.timeout_pwd < Time.now.to_i - last_access
passwd_confirm = ask(I18n.t('cli.interactive.ask_password')) {|q| q.echo = false}
if @passwd.eql?(passwd_confirm)
@ -265,17 +288,17 @@ class Cli
case command[0]
when 'display', 'show', 'd', 's'
if !command[1].nil? && !command[1].empty?
self.display(command[1], group, command[2])
display(command[1], group, command[2])
end
when 'add', 'a'
add()
when 'update', 'u'
if !command[1].nil? && !command[1].empty?
self.update(command[1])
update(command[1])
end
when 'remove', 'delete', 'r', 'd'
if !command[1].nil? && !command[1].empty?
self.remove(command[1])
remove(command[1])
end
when 'group', 'g'
if !command[1].nil? && !command[1].empty?

View file

@ -6,7 +6,6 @@
require 'rubygems'
require 'gpgme'
require 'csv'
require 'yaml'
require 'i18n'
class MPW
@ -20,80 +19,15 @@ class MPW
PASSWORD = 6
PORT = 7
COMMENT = 8
DATE = 9
attr_accessor :error_msg
attr_accessor :timeout_pwd
# 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
# @rtrn: true if le config file is create
def setup(key, lang, file_gpg, timeout_pwd)
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.empty? ? (timeout_pwd = 60) : (timeout_pwd = timeout_pwd.to_i)
config = {'config' => {'key' => key,
'lang' => lang,
'file_gpg' => file_gpg,
'timeout_pwd' => timeout_pwd}}
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
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
def initialize(file_gpg, key=nil)
@error_msg = nil
@file_gpg = file_gpg
@key = key
end
# Decrypt a gpg file
@ -107,10 +41,8 @@ class MPW
crypto = GPGME::Crypto.new(:armor => true)
data_decrypt = crypto.decrypt(IO.read(@file_gpg), :password => passwd).read
id = 0
data_decrypt.lines do |line|
@data[id] = line.parse_csv.unshift(id)
id += 1;
@data.push(line.parse_csv)
end
end
@ -130,7 +62,7 @@ class MPW
data_to_encrypt = ''
@data.each do |row|
data_to_encrypt << row.drop(1).to_csv
data_to_encrypt << row.to_csv
end
crypto.encrypt(data_to_encrypt, :recipients => @key, :output => file_gpg)
@ -147,7 +79,7 @@ class MPW
# @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)
def search(search='', group=nil, protocol=nil)
result = Array.new()
if !search.nil?
@ -174,57 +106,15 @@ class MPW
# @args: id -> the id item
# @rtrn: a row with the resultat of the search
def searchById(id)
if not @data[id.to_i].nil?
return @data[id.to_i]
else
return Array.new
@data.each do |row|
if row[ID] == id
return row
end
end
return Array.new()
end
# Add a new item
# @args: name -> the item name
# group -> the item group
# server -> the ip or server
# protocol -> the protocol
# login -> the login
# passwd -> the password
# port -> the port
# comment -> a comment
# @rtrn: true if it works
def add(name, group=nil, server=nil, protocol=nil, login=nil, passwd=nil, port=nil, comment=nil)
row = Array.new()
if name.nil? || name.empty?
@error_msg = I18n.t('error.add.name_empty')
return false
end
if port.to_i <= 0
port = nil
end
if not @data.last.nil?
id = @data.last
id = id[ID].to_i + 1
else
id = 0
end
row[ID] = id
row[PORT] = port
row[NAME] = name.force_encoding('ASCII-8BIT')
group.nil? || group.empty? ? (row[GROUP] = nil) : (row[GROUP] = group.force_encoding('ASCII-8BIT'))
server.nil? || server.empty? ? (row[SERVER] = nil) : (row[SERVER] = server.force_encoding('ASCII-8BIT'))
protocol.nil? || protocol.empty? ? (row[PROTOCOL] = nil) : (row[PROTOCOL] = protocol.force_encoding('ASCII-8BIT'))
login.nil? || login.empty? ? (row[LOGIN] = nil) : (row[LOGIN] = login.force_encoding('ASCII-8BIT'))
passwd.nil? || passwd.empty? ? (row[PASSWORD] = nil) : (row[PASSWORD] = passwd.force_encoding('ASCII-8BIT'))
comment.nil? || comment.empty? ? (row[COMMENT] = nil) : (row[COMMENT] = comment.force_encoding('ASCII-8BIT'))
@data[id] = row
return true
end
# Update an item
# @args: id -> the item's identifiant
# name -> the item name
@ -236,46 +126,66 @@ class MPW
# port -> the port
# comment -> a comment
# @rtrn: true if the item has been updated
def update(id, name=nil, group=nil, server=nil, protocol=nil, login=nil, passwd=nil, port=nil, comment=nil)
id = id.to_i
def update(name, group, server, protocol, login, passwd, port, comment, id=nil)
row = Array.new()
update = false
if not @data[id].nil?
if port.to_i <= 0
port = nil
i = 0
@data.each do |r|
if r[ID] == id
row = r
update = true
break
end
i += 1
end
row = @data[id]
row_update = Array.new()
if port.to_i <= 0
port = nil
end
name.nil? || name.empty? ? (row_update[NAME] = row[NAME]) : (row_update[NAME] = name)
group.nil? || group.empty? ? (row_update[GROUP] = row[GROUP]) : (row_update[GROUP] = group)
server.nil? || server.empty? ? (row_update[SERVER] = row[SERVER]) : (row_update[SERVER] = server)
protocol.nil? || protocol.empty? ? (row_update[PROTOCOL] = row[PROTOCOL]) : (row_update[PROTOCOL] = protocol)
login.nil? || login.empty? ? (row_update[LOGIN] = row[LOGIN]) : (row_update[LOGIN] = login)
passwd.nil? || passwd.empty? ? (row_update[PASSWORD] = row[PASSWORD]) : (row_update[PASSWORD] = passwd)
port.nil? || port.empty? ? (row_update[PORT] = row[PORT]) : (row_update[PORT] = port)
comment.nil? || comment.empty? ? (row_update[COMMENT] = row[COMMENT]) : (row_update[COMMENT] = comment)
@data[id] = row_update
row_update = Array.new()
row_update[DATE] = Time.now.to_i
return true
else
@error_msg = I18n.t('error.update.id_no_exist', :id => id)
id.nil? || id.empty? ? (row_update[ID] = MPW.generatePassword(16)) : (row_update[ID] = row[ID])
name.nil? || name.empty? ? (row_update[NAME] = row[NAME]) : (row_update[NAME] = name)
group.nil? || group.empty? ? (row_update[GROUP] = row[GROUP]) : (row_update[GROUP] = group)
server.nil? || server.empty? ? (row_update[SERVER] = row[SERVER]) : (row_update[SERVER] = server)
protocol.nil? || protocol.empty? ? (row_update[PROTOCOL] = row[PROTOCOL]) : (row_update[PROTOCOL] = protocol)
login.nil? || login.empty? ? (row_update[LOGIN] = row[LOGIN]) : (row_update[LOGIN] = login)
passwd.nil? || passwd.empty? ? (row_update[PASSWORD] = row[PASSWORD]) : (row_update[PASSWORD] = passwd)
port.nil? || port.empty? ? (row_update[PORT] = row[PORT]) : (row_update[PORT] = port)
comment.nil? || comment.empty? ? (row_update[COMMENT] = row[COMMENT]) : (row_update[COMMENT] = comment)
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)
if not @data.delete_at(id.to_i).nil?
return true
else
@error_msg = I18n.t('error.delete.id_no_exist', :id => id)
return false
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
@ -285,7 +195,7 @@ class MPW
begin
File.open(file, 'w+') do |file|
@data.each do |row|
row.delete_at(ID)
row.delete_at(ID).delete_at(DATE)
file << row.to_csv
end
end
@ -309,7 +219,7 @@ class MPW
return false
else
row = line.parse_csv.unshift(0)
if not add(row[NAME], row[GROUP], row[SERVER], row[PROTOCOL], row[LOGIN], row[PASSWORD], row[PORT], row[COMMENT])
if not update(row[NAME], row[GROUP], row[SERVER], row[PROTOCOL], row[LOGIN], row[PASSWORD], row[PORT], row[COMMENT])
return false
end
end
@ -322,7 +232,7 @@ class MPW
end
end
# Return
# 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 importPreview(file)
@ -349,6 +259,50 @@ class MPW
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
encrypt()
return true
end
# Generate a random password
# @args: length -> the length password
# @rtrn: a random string
@ -367,7 +321,6 @@ class MPW
result << ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]).sample(length).join
return result
#return ([*('A'..'Z'),*('a'..'z'),*('0'..'9')]-%w(0 1 I O l i o)).sample(length).join
end
end

130
lib/MPWConfig.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
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
# @rtrn: true if le config file is create
def setup(key, lang, file_gpg, timeout_pwd)
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.empty? ? (timeout_pwd = 60) : (timeout_pwd = timeout_pwd.to_i)
config = {'config' => {'key' => key,
'lang' => lang,
'file_gpg' => file_gpg,
'timeout_pwd' => timeout_pwd,
'sync_host' => host,
'sync_port' => port,
'sync_pwd' => password,
'sync_suffix' => 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
def setLastUpdate()
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

295
lib/Server.rb Normal file
View file

@ -0,0 +1,295 @@
#!/usr/bin/ruby
require 'socket'
require 'json'
require 'highline/import'
require 'digest'
require "#{APP_ROOT}/lib/MPW.rb"
class Server
attr_accessor :error_msg
# Constructor
def initialize()
YAML::ENGINE.yamler='syck'
end
# Start the server
def start()
server = TCPServer.open(@host, @port)
loop do
Thread.start(server.accept) do |client|
while true do
msg = getClientMessage(client)
if !msg
next
end
if msg['gpg_key'].nil? || msg['gpg_key'].empty? || msg['password'].nil? || msg['password'].empty?
closeConnection(client)
next
end
case msg['action']
when 'get'
client.puts getFile(msg)
when 'update'
client.puts updateFile(msg)
when 'delete'
client.puts deleteFile(msg)
when 'close'
closeConnection(client)
else
client.puts 'Unknown command'
closeConnection(client)
end
end
end
end
end
# Get a gpg file
# @args: msg -> message puts by the client
# @rtrn: json message
def getFile(msg)
gpg_key = msg['gpg_key'].sub('@', '_')
if msg['suffix'].nil? || msg['suffix'].empty?
file_gpg = "#{@data_dir}/#{gpg_key}.yml"
else
file_gpg = "#{@data_dir}/#{gpg_key}-#{msg['suffix']}.yml"
end
if File.exist?(file_gpg)
gpg_data = YAML::load_file(file_gpg)
salt = gpg_data['gpg']['salt']
hash = gpg_data['gpg']['hash']
data = gpg_data['gpg']['data']
if isAuthorized?(msg['password'], salt, hash)
send_msg = {:action => 'get',
:gpg_key => msg['gpg_key'],
:msg => 'done',
:data => data}
else
send_msg = {:action => 'get',
:gpg_key => msg['gpg_key'],
:msg => 'fail',
:error => 'not_authorized'}
end
else
send_msg = {:action => 'get',
:gpg_key => msg['gpg_key'],
:data => '',
:msg => 'fail',
:error => 'file_not_exist'}
end
return send_msg.to_json
end
# Update a file
# @args: msg -> message puts by the client
# @rtrn: json message
def updateFile(msg)
gpg_key = msg['gpg_key'].sub('@', '_')
data = msg['data']
if data.nil? || data.empty?
send_msg = {:action => 'update',
:gpg_key => msg['gpg_key'],
:msg => 'fail',
:error => 'no_data'}
return send_msg.to_json
end
if msg['suffix'].nil? || msg['suffix'].empty?
file_gpg = "#{@data_dir}/#{gpg_key}.yml"
else
file_gpg = "#{@data_dir}/#{gpg_key}-#{msg['suffix']}.yml"
end
if File.exist?(file_gpg)
gpg_data = YAML::load_file(file_gpg)
salt = gpg_data['gpg']['salt']
hash = gpg_data['gpg']['hash']
else
salt = MPW.generatePassword(4)
hash = Digest::SHA256.hexdigest(salt + msg['password'])
end
if isAuthorized?(msg['password'], salt, hash)
begin
config = {'gpg' => {'salt' => salt,
'hash' => hash,
'data' => data}}
File.open(file_gpg, 'w+') do |file|
file << config.to_yaml
end
send_msg = {:action => 'update',
:gpg_key => msg['gpg_key'],
:msg => 'done'}
rescue Exception => e
send_msg = {:action => 'update',
:gpg_key => msg['gpg_key'],
:msg => 'fail',
:error => 'server_error'}
end
else
send_msg = {:action => 'update',
:gpg_key => msg['gpg_key'],
:msg => 'fail',
:error => 'not_autorized'}
end
return send_msg.to_json
end
# Remove a gpg file
# @args: msg -> message puts by the client
# @rtrn: json message
def deleteFile(msg)
gpg_key = msg['gpg_key'].sub('@', '_')
if msg['suffix'].nil? || msg['suffix'].empty?
file_gpg = "#{@data_dir}/#{gpg_key}.yml"
else
file_gpg = "#{@data_dir}/#{gpg_key}-#{msg['suffix']}.yml"
end
if !File.exist?(file_gpg)
send_msg = {:action => 'delete',
:gpg_key => msg['gpg_key'],
:msg => 'delete_fail',
:error => 'file_not_exist'}
return send_msg.to_json
end
gpg_data = YAML::load_file(file_gpg)
salt = gpg_data['gpg']['salt']
hash = gpg_data['gpg']['hash']
if isAuthorized?(msg['password'], salt, hash)
begin
File.unlink(file_gpg)
send_msg = {:action => 'delete',
:gpg_key => msg['gpg_key'],
:msg => 'delete_done'}
rescue Exception => e
send_msg = {:action => 'delete',
:gpg_key => msg['gpg_key'],
:msg => 'delete_fail',
:error => e}
end
else
send_msg = {:action => 'delete',
:gpg_key => msg['gpg_key'],
:msg => 'delete_fail',
:error => 'not_autorized'}
end
return send_msg.to_json
end
# Check is the hash equal the password with the salt
# @args: password -> the user password
# salt -> the salt
# hash -> the hash of the password with the salt
# @rtrn: true is is good, else false
def isAuthorized?(password, salt, hash)
if hash == Digest::SHA256.hexdigest(salt + password)
return true
else
return false
end
end
# Get message to client
# @args: client -> client connection
# @rtrn: array of the json string, or false if isn't json message
def getClientMessage(client)
begin
msg = client.gets
return JSON.parse(msg)
rescue
closeConnection(client)
return false
end
end
# Close the client connection
# @args: client -> client connection
def closeConnection(client)
client.puts "Closing the connection. Bye!"
client.close
end
# Check the config file
# @args: file_config -> the configuration file
# @rtrn: true if the config file is correct
def checkconfig(file_config)
begin
config = YAML::load_file(file_config)
@host = config['config']['host']
@port = config['config']['port'].to_i
@data_dir = config['config']['data_dir']
@timeout = config['config']['timeout'].to_i
if @host.empty? || @port <= 0 || @data_dir.empty?
puts I18n.t('server.checkconfig.fail')
puts I18n.t('server.checkconfig.empty')
return false
end
if !Dir.exist?(@data_dir)
puts I18n.t('server.checkconfig.fail')
puts I18n.t('server.checkconfig.datadir')
return false
end
rescue Exception => e
puts "#{I18n.t('server.checkconfig.fail')}\n#{e}"
return false
end
return true
end
# Create a new config file
# @args: file_config -> the configuration file
# @rtrn: true if le config file is create
def setup(file_config)
puts I18n.t('server.form.setup.title')
puts '--------------------'
host = ask(I18n.t('server.form.setup.host')).to_s
port = ask(I18n.t('server.form.setup.port')).to_s
data_dir = ask(I18n.t('server.form.setup.data_dir')).to_s
timeout = ask(I18n.t('server.form.setup.timeout')).to_s
config = {'config' => {'host' => host,
'port' => port,
'data_dir' => data_dir,
'timeout' => timeout}}
begin
File.open(file_config, 'w') do |file|
file << config.to_yaml
end
rescue Exception => e
puts "#{I18n.t('server.formsetup.not_valid')}\n#{e}"
return false
end
return true
end
end

133
lib/Sync.rb Normal file
View file

@ -0,0 +1,133 @@
#!/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
# Constructor
def initialize()
@error_msg = nil
end
# Disable the sync
def disable()
@sync = 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)
@sync = true
rescue Exception => e
@error_msg = "#{I18n.t('error.sync.connection')}\n#{e}"
@sync = false
end
return @sync
end
# Get data on server
# @args: gpg_password -> the gpg password
# @rtrn: nil if nothing data or error
def get(gpg_password)
if !@sync
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)
case msg['error']
when nil, 'file_not_exist'
tmp_file = "/tmp/mpw-#{MPW.generatePassword()}.gpg"
File.open(tmp_file, 'w') do |file|
file << msg['data']
end
@mpw = MPW.new(tmp_file)
if !@mpw.decrypt(gpg_password)
return nil
end
File.unlink(tmp_file)
return @mpw.search()
when 'not_authorized'
@error_msg = "#{I18n.t('error.sync.not_authorized')}\n#{e}"
else
@error_msg = "#{I18n.t('error.sync.unknown')}\n#{e}"
end
return nil
end
# Update the remote data
# @args: data -> the data to send on server
# @rtrn: false if there is a problem
def update(data)
if !@sync
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)
case msg['error']
when nil
return true
when 'not_authorized'
@error_msg = "#{I18n.t('error.sync.not_authorized')}\n#{e}"
when 'no_data'
@error_msg = "#{I18n.t('error.sync.no_data')}\n#{e}"
else
@error_msg = "#{I18n.t('error.sync.unknown')}\n#{e}"
end
return false
end
def delete()
end
# Close the connection
def close()
if !@sync
return
end
send_msg = {:action => 'close'}
@socket.puts send_msg.to_json
end
end

23
mpw
View file

@ -93,13 +93,17 @@ OptionParser.new do |opts|
end
end.parse!
config = MPWConfig.new(options[:config])
check_error = config.checkconfig()
cli = Cli.new(lang, options[:config])
cli = Cli.new(lang, config)
cli.sync()
# Setup a new config
if !check_error || !options[:setup].nil?
cli.setup(lang)
# Display the item's informations
if not options[:setup].nil?
cli.setup()
elsif not options[:display].nil?
cli.display(options[:display], options[:group], options[:type], options[:format])
@ -125,7 +129,16 @@ elsif not options[:import].nil?
# Interactive mode
else
cli.interactive
begin
cli.interactive()
rescue SystemExit, Interrupt
cli.sync()
cli = nil
return 1
end
end
cli.sync()
cli = nil
exit 0

60
mpw-server Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/ruby
# author: nishiki
# mail: nishiki@yaegashi.fr
# info: a simple script who manage your passwords
require 'rubygems'
require 'optparse'
require 'pathname'
require 'locale'
require 'i18n'
APP_ROOT = File.dirname(Pathname.new(__FILE__).realpath)
require "#{APP_ROOT}/lib/Server.rb"
lang = Locale::Tag.parse(ENV['LANG']).to_simple.to_s[0..1]
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.load_path = Dir["#{APP_ROOT}/i18n/*.yml"]
I18n.default_locale = :en
I18n.locale = lang.to_sym
options = {}
OptionParser.new do |opts|
opts.banner = "#{I18n.t('server.option.usage')}: mpw-server -c CONFIG [options]"
opts.on("-c", "--config CONFIG", I18n.t('server.option.config')) do |config|
options[:config] = config
end
opts.on("-t", "--checkconfig", I18n.t('server.option.checkconfig')) do |b|
options[:checkconfig] = b
end
opts.on("-s", "--setup", I18n.t('server.option.setup')) do |b|
options[:setup] = b
end
opts.on("-h", "--help", I18n.t('server.option.help')) do |b|
puts opts
exit 0
end
end.parse!
if options[:config].nil? || options[:config].empty?
puts "#{I18n.t('server.option.usage')}: mpw-server -c CONFIG [options]"
exit 2
end
server = Server.new
if options[:checkconfig]
server.checkconfig(options[:config])
elsif options[:setup]
server.setup(options[:config])
else
server.checkconfig(options[:config])
server.start()
end
exit 0