diff --git a/lib/Cli.rb b/lib/Cli.rb index 4947c00..aab3242 100644 --- a/lib/Cli.rb +++ b/lib/Cli.rb @@ -8,9 +8,11 @@ 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 @@ -29,6 +31,23 @@ class Cli puts "#{I18n.t('cli.display.error')}: #{@mpw.error_msg}" exit 2 end + + @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}" + else + 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}" + else + @sync.close() + end + end end # Create a new config file diff --git a/lib/MPW.rb b/lib/MPW.rb index 1bd4392..1d15076 100644 --- a/lib/MPW.rb +++ b/lib/MPW.rb @@ -79,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? @@ -107,7 +107,7 @@ class MPW # @rtrn: a row with the resultat of the search def searchById(id) @data.each do |row| - if @data[ID] == id + if row[ID] == id return row end end @@ -168,14 +168,15 @@ class MPW i = 0 @data.each do |row| - if not row[ID] == id + if row[ID] == id if port.to_i <= 0 port = nil end - row_update = Array.new() - row[DATE] = Time.now.to_i + row_update = Array.new() + row_update[ID] = row[ID] + row_update[DATE] = Time.now.to_i 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) @@ -205,7 +206,7 @@ class MPW i = 0 @data.each do |row| if row[ID] == id - @data.delete(i) + @data.delete_at(i) return true end i += 1 @@ -286,6 +287,51 @@ 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 + self.update(l[ID], r[NAME], r[GROUP], r[SERVER], r[PROTOCOL], r[LOGIN], r[PASSWORD], r[PORT], r[COMMENT]) + 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 + self.remove(l[ID]) + end + end + + # Add item + data_remote.each do |r| + if r[DATE].to_i > last_update + puts 'add' + @data.push(r) + end + end + + self.encrypt() + + return true + end + # Generate a random password # @args: length -> the length password # @rtrn: a random string diff --git a/lib/MPWConfig.rb b/lib/MPWConfig.rb index a402ee3..790f34e 100644 --- a/lib/MPWConfig.rb +++ b/lib/MPWConfig.rb @@ -15,11 +15,12 @@ class MPWConfig 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_sufix - attr_accessor :sync_last_update + attr_accessor :sync_suffix + attr_accessor :last_update # Constructor # @args: file_config -> the specify config file @@ -78,15 +79,15 @@ class MPWConfig 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_sufix = config['config']['sync_suffix'] - @sync_last_update = config['config']['sync_last_update'].to_i + @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_sufix = 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') @@ -102,5 +103,28 @@ class MPWConfig 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_uffix, + '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 diff --git a/lib/Server.rb b/lib/Server.rb index 361835a..59d377c 100644 --- a/lib/Server.rb +++ b/lib/Server.rb @@ -21,29 +21,33 @@ class Server server = TCPServer.open(@host, @port) loop do Thread.start(server.accept) do |client| - msg = self.getClientMessage(client) + while true do + msg = self.getClientMessage(client) - if !msg - next - end - - if msg['gpg_key'].nil? || msg['gpg_key'].empty? || msg['password'].nil? || msg['password'].empty? - self.closeConnection(client) - next - end + if !msg + next + end + + if msg['gpg_key'].nil? || msg['gpg_key'].empty? || msg['password'].nil? || msg['password'].empty? + self.closeConnection(client) + next + end - case msg['action'] - when 'get' - client.puts self.getFile(msg) - when 'update' - client.puts self.updateFile(msg) - when 'delete' - client.puts self.deleteFile(msg) - else - client.puts 'Unknown command' + case msg['action'] + when 'get' + client.puts self.getFile(msg) + when 'update' + client.puts self.updateFile(msg) + puts 'update' + when 'delete' + client.puts self.deleteFile(msg) + when 'close' + self.closeConnection(client) + else + client.puts 'Unknown command' + self.closeConnection(client) + end end - - self.closeConnection(client) end end end @@ -68,11 +72,11 @@ class Server last_update = gpg_data['gpg']['last_update'] if self.isAuthorized?(msg['password'], salt, hash) - send_msg = {:action => 'get', - :gpg_key => msg['gpg_key'], + send_msg = {:action => 'get', + :gpg_key => msg['gpg_key'], :last_update => last_update, - :msg => 'done', - :data => data} + :msg => 'done', + :data => data} else send_msg = {:action => 'get', :gpg_key => msg['gpg_key'], @@ -80,10 +84,12 @@ class Server :error => 'not_authorized'} end else - send_msg = {:action => 'get', - :gpg_key => msg['gpg_key'], - :msg => 'fail', - :error => 'file_not_exist'} + send_msg = {:action => 'get', + :gpg_key => msg['gpg_key'], + :last_update => 0, + :data => '', + :msg => 'fail', + :error => 'file_not_exist'} end return send_msg.to_json @@ -129,7 +135,7 @@ class Server 'last_update' => last_update, 'data' => data}} - File.open(file_gpg, 'w') do |file| + File.open(file_gpg, 'w+') do |file| file << config.to_yaml end @@ -242,11 +248,11 @@ class Server begin config = YAML::load_file(file_config) @host = config['config']['host'] - @port = config['config']['port'] + @port = config['config']['port'].to_i @data_dir = config['config']['data_dir'] @timeout = config['config']['timeout'].to_i - if @host.empty? || @port.empty? || @data_dir.empty? + if @host.empty? || @port <= 0 || @data_dir.empty? puts I18n.t('server.checkconfig.fail') puts I18n.t('server.checkconfig.empty') return false diff --git a/lib/Sync.rb b/lib/Sync.rb new file mode 100644 index 0000000..99f7d81 --- /dev/null +++ b/lib/Sync.rb @@ -0,0 +1,105 @@ +#!/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 + + def initialize() + @error_msg = nil + end + + def disable() + @sync = false + end + + 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 = "ERROR: Connection impossible\n#{e}" + @sync = false + end + + return @sync + end + + def get(gpg_password) + 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 = 'not authorized' + else + @error_msg = 'error unknow' + end + + return nil + end + + def update(data) + 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 = 'not authorized' + when 'no_data' + @error_msg = 'no data' + else + @error_msg = 'error unknow' + end + + return false + end + + def delete() + end + + def close() + send_msg = {:action => 'close'} + @socket.puts send_msg.to_json + end +end