diff --git a/MPW.rb b/MPW.rb new file mode 100755 index 0000000..c84ccea --- /dev/null +++ b/MPW.rb @@ -0,0 +1,310 @@ +#!/usr/bin/ruby +# author: nishiki +# mail: nishiki@yaegashi.fr +# info: a simple script who manage your passwords + +require 'rubygems' +require 'gpgme' +require 'csv' +require 'net/ssh' +require 'yaml' + +class MPW + + ID = 0 + PROTOCOL = 1 + SERVER = 2 + LOGIN = 3 + PASSWORD = 4 + PORT = 5 + COMMENT = 6 + + attr_accessor :error + attr_accessor :error_msg + + # Constructor + def initialize() + @file_config = "#{Dir.home()}/.mpw.cfg" + @error_mgs = nil + @error = 0 + end + + # Create a new config file + # @args: key -> the gpg key to encrypt + # file_gpg -> the file who is encrypted + # file_pwd -> the file who stock the password + # timeout_pwd -> time to save the password + # @rtrn: true if le config file is create + def setup(key, file_gpg, file_pwd, timeout_pwd) + + if not key =~ /[a-zA-Z0-9.-_]+\@[a-zA-Z0-9]+\.[a-zA-Z]+/ + @error_msg = "The key string isn't in good format!" + @error = 1 + return false + end + + if file_gpg.empty? + file_gpg = "#{Dir.home()}/.mpw.gpg" + end + + if file_pwd.empty? + file_pwd = "#{Dir.home()}/.mpw.pwd" + end + + timeout_pwd.empty? ? (timeout_pwd = 300) : (timeout_pwd = timeout_pwd.to_i) + + config = {'config' => {'key' => key, + 'file_gpg' => file_gpg, + 'timeout_pwd' => timeout_pwd, + 'file_pwd' => file_pwd}} + + begin + File.open(@file_config, 'w') do |file| + file << config.to_yaml + end + rescue + @error_msg = "Can't write the config file!" + @error = 2 + 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'] + @file_gpg = config['config']['file_gpg'] + @file_pwd = config['config']['file_pwd'] + @timeout_pwd = config['config']['timeout_pwd'].to_i + + if @key.empty? || @file_gpg.empty? || @file_pwd.empty? + return false + end + + rescue + @error_msg = "Checkconfig failed!" + @error = 3 + return false + end + + return true + end + + # Decrypt a gpg file + # @args: password -> the GPG key password + # @rtrn: true if data is decrypted + def decrypt(passwd=nil) + @data = "" + + begin + if passwd.nil? + passwd = IO.read(@file_pwd) + end + rescue + if not passwd.nil? + file_pwd = File.new(@file_pwd, 'w') + File.chmod(0600, @file_pwd) + file_pwd << passwd + file_pwd.close + end + end + + begin + if File.exist?(@file_gpg) + crypto = GPGME::Crypto.new(:armor => true) + @data = crypto.decrypt(IO.read(@file_gpg), :password => passwd).read + end + return true + rescue + if not @file_pwd.nil? + File.delete(@file_pwd) + end + + @error_msg = "Can't decrypt file!" + @error = 4 + return false + end + end + + # Check if a password it saved + # @rtrn: true if a password exist in the password file + def checkFilePassword() + if !@file_pwd.nil? && File.exist?(@file_pwd) && File.stat(@file_pwd).mtime.to_i + @timeout_pwd < Time.now.to_i + File.delete(@file_pwd) + return false + end + + return true + end + + # Encrypt a file + def encrypt() + begin + crypto = GPGME::Crypto.new(:armor => true) + file_gpg = File.open(@file_gpg, 'w+') + crypto.encrypt(@data, :recipients => @key, :output => file_gpg) + file_gpg.close + + return true + rescue + @error_msg = "Can't encrypt the GPG file!" + @error = 5 + return false + end + end + + # Search in some csv data + # @args: search -> the string to search + # type -> the connection type (ssh, web, other) + # @rtrn: a list with the resultat of the search + def search(search, protocol=nil) + result = Array.new() + @data.lines do |line| + row = line.parse_csv + if line =~ /^.*#{search}.*$/ || protocol.eql?('all') + if protocol.nil? || protocol.eql?(row[PROTOCOL]) || protocol.eql?('all') + 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 searchById(id) + @data.lines do |line| + row = line.parse_csv + if !id.nil? && id.eql?(row[ID]) + return row + end + end + + return Array.new() + end + + # Add a new item + # @args: server -> the ip or server + # protocol -> the protocol + # login -> the login + # passwd -> the password + # port -> the port + # comment -> a comment + def add(server, protocol=nil, login=nil, passwd=nil, port=nil, comment=nil) + row = Array.new() + + row[ID] = Time.now.to_i.to_s(16) + row[SERVER] = server + row[PROTOCOL] = protocol + row[LOGIN] = login + row[PASSWORD] = passwd + row[PORT] = port + row[COMMENT] = comment + + @data << "#{row.join(',')}\n" + end + + # Update an item + # @args: id -> the item's identifiant + # server -> the ip or server + # 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(id, server=nil, protocol=nil, login=nil, passwd=nil, port=nil, comment=nil) + updated = false + data_tmp = '' + + @data.lines do |line| + row = line.parse_csv + if id.eql?(row[ID]) + row_update = Array.new() + + row_update[ID] = row[ID] + server.empty? ? (row_update[SERVER] = row[SERVER]) : (row_update[SERVER] = server) + protocol.empty? ? (row_update[PROTOCOL] = row[PROTOCOL]) : (row_update[PROTOCOL] = protocol) + login.empty? ? (row_update[LOGIN] = row[LOGIN]) : (row_update[LOGIN] = login) + passwd.empty? ? (row_update[PASSWORD] = row[PASSWORD]) : (row_update[PASSWORD] = passwd) + port.empty? ? (row_update[PORT] = row[PORT]) : (row_update[PORT] = port) + comment.empty? ? (row_update[COMMENT] = row[COMMENT]) : (row_update[COMMENT] = comment) + + data_tmp << "#{row_update.join(',')}\n" + updated = true + else + data_tmp << line + end + end + @data = data_tmp + + if not updated + @error_msg = "Can't update the item: #{id}!" + @error = 6 + end + + return updated + end + + # Remove an item + # @args: id -> the item's identifiant + # @rtrn: true if the item has been deleted + def remove(id) + removed = false + data_tmp = "" + + @data.lines do |line| + row = line.parse_csv + if id.eql?(row[ID]) + removed = true + else + data_tmp << line + end + end + @data = data_tmp + + if not removed + @error_msg = "Can't remove the item: #{id}!" + @error = 7 + end + + return removed + end + + # Connect to ssh && display the password + # @args: search -> a string to match + # @rtrn: true if ssh connection work + def ssh(search) + result = self.search(search, 'ssh') + + if result.length > 0 + result.each do |r| + server = r[SERVER] + login = r[LOGIN] + port = r[PORT] + passwd = r[PASSWORD] + + if port.empty? + port = 22 + end + + if passwd.empty? + system("#{passwd} ssh #{login}@#{server} -p #{port}") + else + system("sshpass -p #{passwd} ssh #{login}@#{server} -p #{port}") + end + end + + return true + else + return false + end + end + +end diff --git a/cli.rb b/cli.rb index 15ba10a..af7b98d 100755 --- a/cli.rb +++ b/cli.rb @@ -1,28 +1,24 @@ #!/usr/bin/ruby # author: nishiki # mail: nishiki@yaegashi.fr -# info: a simple script who manage your passwords +# info: a simple script who m your passwords require 'rubygems' require 'highline/import' -require 'yaml' +require './MPW.rb' class Cli - attr_accessor :key - attr_accessor :file_gpg - attr_accessor :file_pwd - attr_accessor :timeout_pwd - def initialize() - @file_config = "#{Dir.home()}/.mpw.cfg" - - if !File.exist?(@file_config) || !self.checkconfig() + @m = MPW.new() + + if not @m.checkconfig() self.setup() - if not self.checkconfig() - puts "Error during the checkconfig post setup!" - exit 2 - end + end + + if not self.decrypt() + puts "ERROR: #{@m.error_msg}" + exit 2 end end @@ -32,50 +28,102 @@ class Cli file_gpg = ask("Enter the path to encrypt file [default=#{Dir.home()}/.mpw.gpg]: ") file_pwd = ask("Enter te path to password file [default=#{Dir.home()}/.mpw.pwd]: ") timeout_pwd = ask("Enter the timeout (in seconde) to GPG password [default=300]: ") - - if not key =~ /[a-zA-Z0-9.-_]*\@[a-zA-Z0-9]*\.[a-zA-Z]*/ - puts "GPG key is invalid!" - exit 2 - end - if file_gpg.empty? - file_gpg = "#{Dir.home()}/.mpw.gpg" - end - - if file_pwd.empty? - file_pwd = "#{Dir.home()}/.mpw.pwd" - end - - timeout_pwd.empty? ? (timeout_pwd = 300) : (timeout_pwd = timeout_pwd.to_i) - - config = {'config' => {'key' => key, - 'file_gpg' => file_gpg, - 'timeout_pwd' => timeout_pwd, - 'file_pwd' => file_pwd}} - - File.open(@file_config, 'w') do |file| - file << config.to_yaml + if setup(key, file_gpg, file_pwd, timeout_pwd) + puts "The config file has been created!" + else + puts "ERROR: #{@m.error_msg}" end 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'] - @file_gpg = config['config']['file_gpg'] - @file_pwd = config['config']['file_pwd'] - @timeout_pwd = config['config']['timeout_pwd'].to_i - - if @key.empty? || @file_gpg.empty? || @file_pwd.empty? - return false - end - - rescue - return false + def decrypt() + if not @m.checkFilePassword() + passwd = ask("Password GPG: ") {|q| q.echo = false} + return @m.decrypt(passwd) + else + return @m.decrypt() end + end - return true + def display(search, protocol=nil) + result = @m.search(search, protocol) + + if not result.empty? + result.each do |r| + puts "# --------------------" + puts "# Id: #{r[MPW::ID]}" + puts "# Server: #{r[MPW::SERVER]}" + puts "# Type: #{r[MPW::PROTOCOL]}" + puts "# Login: #{r[MPW::LOGIN]}" + puts "# Password: #{r[MPW::PASSWORD]}" + puts "# Port: #{r[MPW::PORT]}" + puts "# Comment: #{r[MPW::COMMENT]}" + end + else + puts "Nothing result!" + end + end + + def add() + row = Array.new() + puts "# Add a new item" + puts "# --------------------" + server = ask("Enter the server name or ip: ") + protocol = ask("Enter the type of connection (ssh, web, other): ") + login = ask("Enter the login connection: ") + passwd = ask("Enter the the password: ") + port = ask("Enter the connection port (optinal): ") + comment = ask("Enter a comment (optinal): ") + + @m.add(server, protocol, login, passwd, port, comment) + + if @m.encrypt() + puts "Item has been added!" + else + puts "ERROR: #{@m.error_msg}" + end + end + + def update(id) + row = @m.searchById(id) + + if not row.empty? + puts "# Add a new password" + puts "# --------------------" + server = ask("Enter the server name or ip [#{row[MPW::SERVER]}]: ") + protocol = ask("Enter the type of connection [#{row[MPW::PROTOCOL]}]: ") + login = ask("Enter the login connection [#{row[MPW::LOGIN]}]: ") + passwd = ask("Enter the the password: ") + port = ask("Enter the connection port [#{row[MPW::PORT]}]: ") + comment = ask("Enter a comment [#{row[MPW::COMMENT]}]: ") + + if @m.update(id, server, protocol, login, passwd, port, comment) + if @m.encrypt() + puts "Item has been updated!" + else + puts "ERROR: #{@m.error_msg}" + end + else + puts "Nothing item has been updated!" + end + else + puts "Nothing result!" + end + end + + def remove(id) + if @m.remove(id) + if @m.encrypt() + puts "The item #{id} has been removed!" + else + puts "ERROR: #{@m.error_msg}" + end + else + puts "Nothing item has been removed!" + end + end + + def ssh(search) + @m.ssh(search) end end diff --git a/manage-password.rb b/manage-password.rb deleted file mode 100755 index 0997925..0000000 --- a/manage-password.rb +++ /dev/null @@ -1,235 +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 'net/ssh' -require 'highline/import' - -class ManagePasswd - - ID = 0 - TYPE = 1 - SERVER = 2 - LOGIN = 3 - PASSWORD = 4 - PORT = 5 - COMMENT = 6 - - # Constructor - # @args: key -> the gpg key to encrypt - # file_gpg -> the file who is encrypted - # file_pwd -> the file who stock the password - # timeout_pwd -> time to save the password - def initialize(key, file_gpg, file_pwd, timeout_pwd=300) - @key = key - @file_gpg = file_gpg - @file_pwd = file_pwd - @timeout_pwd = timeout_pwd - - if File.exist?(@file_gpg) - if not self.decrypt() - exit 2 - end - else - @data = "" - end - end - - # Decrypt a gpg file - # @rtrn: true if data is decrypted - def decrypt() - if File.exist?(@file_pwd) && File.stat(@file_pwd).mtime.to_i + @timeout_pwd < Time.now.to_i - File.delete(@file_pwd) - end - - begin - passwd = IO.read(@file_pwd) - rescue - passwd = ask("Password GPG: ") {|q| q.echo = false} - file_pwd = File.new(@file_pwd, 'w+') - File.chmod(0600, @file_pwd) - file_pwd << passwd - file_pwd.close - end - - begin - crypto = GPGME::Crypto.new(:armor => true) - @data = crypto.decrypt(IO.read(@file_gpg), :password => passwd).read - - return true - rescue - puts "Your passphrase is probably wrong!" - File.delete(@file_pwd) - - return false - end - end - - # Encrypt a file - def encrypt() - begin - crypto = GPGME::Crypto.new(:armor => true) - file_gpg = File.open(@file_gpg, 'w+') - crypto.encrypt(@data, :recipients => @key, :output => file_gpg) - file_gpg.close - - return true - rescue - puts "Error during the encrypting file" - return false - end - end - - # Search in some csv data - # @args: search -> the string to search - # type -> the connection type (ssh, web, other) - # @rtrn: a list with the resultat of the search - def search(search, type=nil) - result = Array.new() - @data.lines do |line| - row = line.parse_csv - if line =~ /^.*#{search}.*$/ || type.eql?('all') - if type.nil? || type.eql?(row[TYPE]) || type.eql?('all') - result.push(row) - end - end - end - - return result - end - - # Display the connections informations for a server - # @args: search -> a string to match - # type -> search for a type item - def display(search, type=nil) - result = self.search(search, type) - - if result.length > 0 - result.each do |r| - puts "# --------------------" - puts "# Id: #{r[ID]}" - puts "# Server: #{r[SERVER]}" - puts "# Type: #{r[TYPE]}" - puts "# Login: #{r[LOGIN]}" - puts "# Password: #{r[PASSWORD]}" - puts "# Port: #{r[PORT]}" - puts "# Comment: #{r[COMMENT]}" - end - - return true - else - puts "Nothing result!" - return false - end - end - - # Add a new item - def add() - row = Array.new() - puts "# Add a new password" - puts "# --------------------" - row[ID] = Time.now.to_i.to_s(16) - row[SERVER] = ask("Enter the server name or ip: ") - row[TYPE] = ask("Enter the type of connection (ssh, web, other): ") - row[LOGIN] = ask("Enter the login connection: ") - row[PASSWORD] = ask("Enter the the password: ") - row[PORT] = ask("Enter the connection port (optinal): ") - row[COMMENT] = ask("Enter a comment (optinal): ") - - @data << "#{row.join(',')}\n" - puts 'Item has been added!' - end - - # Update an item - # @args: id -> the item's identifiant - def update(id) - data_tmp = '' - @data.lines do |line| - row = line.parse_csv - if id.eql?(row[ID]) - row_update = Array.new() - - puts "# Add a new password" - puts "# --------------------" - server = ask("Enter the server name or ip [#{row[SERVER]}]: ") - type = ask("Enter the type of connection [#{row[TYPE]}]: ") - login = ask("Enter the login connection [#{row[LOGIN]}]: ") - passwd = ask("Enter the the password: ") - port = ask("Enter the connection port [#{row[PORT]}]: ") - comment = ask("Enter a comment [#{row[COMMENT]}]: ") - - row_update[ID] = row[ID] - server.empty? ? (row_update[SERVER] = row[SERVER]) : (row_update[SERVER] = server) - type.empty? ? (row_update[TYPE] = row[TYPE]) : (row_update[TYPE] = type) - login.empty? ? (row_update[LOGIN] = row[LOGIN]) : (row_update[LOGIN] = login) - passwd.empty? ? (row_update[PASSWORD] = row[PASSWORD]) : (row_update[PASSWORD] = passwd) - port.empty? ? (row_update[PORT] = row[PORT]) : (row_update[PORT] = port) - comment.empty? ? (row_update[COMMENT] = row[COMMENT]) : (row_update[COMMENT] = comment) - - data_tmp << "#{row_update.join(',')}\n" - else - data_tmp << line - end - end - @data = data_tmp - puts 'Item has been updated!' - end - - # Remove an item - # @args: id -> the item's identifiant - def remove(id) - data_tmp = '' - @data.lines do |line| - row = line.parse_csv - if id.eql?(row[ID]) - puts "The item #{row[ID]} has been removed!" - else - data_tmp << line - end - end - @data = data_tmp - end - - # Connect to ssh && display the password - # @args: search -> a string to match - def ssh(search) - result = self.search(search, 'ssh') - - if result.length > 0 - result.each do |r| - server = r[SERVER] - login = r[LOGIN] - port = r[PORT] - passwd = r[PASSWORD] - - if port.empty? - port = 22 - end - - if passwd.empty? - system("#{passwd} ssh #{login}@#{server} -p #{port}") - else - system("sshpass -p #{passwd} ssh #{login}@#{server} -p #{port}") - end - #Net::SSH.start(server, login, :port => port, :password => passwd) do |ssh| - # channel = ssh.open_channel do |ch| - # puts ch.exec 'ls' - # ch.on_data do |data| - # $stdout.print data - # end - # end - #end - end - - return true - else - puts "Nothing result!" - return false - end - end - -end diff --git a/mpw b/mpw index f171e01..271127f 100755 --- a/mpw +++ b/mpw @@ -5,12 +5,11 @@ require 'rubygems' require 'optparse' -require './manage-password.rb' require './cli.rb' options = {} OptionParser.new do |opts| - opts.banner = "Usage: manage-password.rb [options]" + opts.banner = "Usage: mpw [options]" opts.on("-d", "--display [SEARCH]", "Display items") do |search| search.nil? ? (options[:display] = '') : (options[:display] = search) @@ -33,48 +32,52 @@ OptionParser.new do |opts| options[:add] = true end - opts.on("-t", "--type TYPE", "select an type") do |type| + opts.on("-s", "--ssh SEARCH", "Connect to ssh") do |search| + options[:ssh] = search + end + + opts.on("-p", "--protocol PROTOCOL", "Select a protocol") do |type| options[:type] = type end + opts.on("-f", "--force", "Force an action") do |b| + options[:force] = true + end + opts.on("-h", "--help", "Show this message") do |b| puts opts exit 0 end end.parse! -cli = Cli.new - -manage = ManagePasswd.new(cli.key, cli.file_gpg, cli.file_pwd) +cli = Cli.new() +puts options # Display the item's informations if not options[:display].nil? if not options[:type].nil? - manage.display(options[:display], options[:type]) + cli.display(options[:display], options[:type]) elsif not options[:showall].nil? - manage.display(options[:display], 'all') + cli.display(options[:display], 'all') else - manage.display(options[:display]) + cli.display(options[:display]) end # Remove an item elsif not options[:remove].nil? - manage.remove(options[:remove]) - manage.encrypt() + cli.remove(options[:remove]) # Update an item elsif not options[:update].nil? - manage.update(options[:update]) - manage.encrypt() + cli.update(options[:update]) # Connect to ssh elsif not options[:ssh].nil? - manage.ssh(options[:ssh]) + cli.ssh(options[:ssh]) # Add a new item elsif not options[:add].nil? - manage.add() - manage.encrypt() + cli.add() else puts "For help add option -h or --help"