pwss 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +9 -18
- data/.travis.yml +4 -0
- data/LICENSE.txt +17 -18
- data/README.md +454 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/pwss +6 -0
- data/lib/pwss.rb +17 -127
- data/lib/pwss/cipher.rb +11 -74
- data/lib/pwss/cli/command_semantics.rb +402 -0
- data/lib/pwss/cli/command_syntax.rb +156 -0
- data/lib/pwss/fileops.rb +31 -22
- data/lib/pwss/generators/bank_account.rb +18 -0
- data/lib/pwss/generators/code.rb +13 -0
- data/lib/pwss/generators/credit_card.rb +23 -0
- data/lib/pwss/generators/entry.rb +36 -0
- data/lib/pwss/generators/fields.rb +119 -0
- data/lib/pwss/generators/sim.rb +15 -0
- data/lib/pwss/generators/software_license.rb +19 -0
- data/lib/pwss/password.rb +94 -0
- data/lib/pwss/safe.rb +183 -0
- data/lib/pwss/version.rb +1 -1
- data/pwss.gemspec +19 -13
- metadata +85 -25
- data/README.textile +0 -190
- data/bin/pwss +0 -445
- data/lib/pwss/bank_account.rb +0 -18
- data/lib/pwss/credit_card.rb +0 -23
- data/lib/pwss/entry.rb +0 -69
- data/lib/pwss/software_license.rb +0 -21
data/Rakefile
CHANGED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pwss"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/pwss
ADDED
data/lib/pwss.rb
CHANGED
@@ -1,127 +1,17 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
def self.update search_string, entries, length, alnum
|
21
|
-
id = choose_entry search_string, entries, true
|
22
|
-
|
23
|
-
password = Cipher.check_or_generate "new password for entry", length, alnum
|
24
|
-
|
25
|
-
entries[id]["password"] = password
|
26
|
-
entries[id]["updated_at"] = Date.today
|
27
|
-
|
28
|
-
return entries, password
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.update_field search_string, entries, field
|
32
|
-
id = choose_entry search_string, entries, true
|
33
|
-
|
34
|
-
field_value = Readline.readline("\nEnter new value for #{field}: ")
|
35
|
-
|
36
|
-
entries[id][field] = field_value
|
37
|
-
entries[id]["updated_at"] = Date.today
|
38
|
-
password = entries[id]["password"]
|
39
|
-
|
40
|
-
return entries, password
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
def self.destroy search_string, entries
|
45
|
-
id = choose_entry search_string, entries, true
|
46
|
-
|
47
|
-
entries.delete_at(id) if id != -1
|
48
|
-
entries
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
def self.list entries
|
53
|
-
index = 0
|
54
|
-
entries.each do |element|
|
55
|
-
print_entry index, element
|
56
|
-
index += 1
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
#
|
63
|
-
# Let the user select an entry from data
|
64
|
-
# (data is a YAML string with an array of entries)
|
65
|
-
#
|
66
|
-
def self.choose_entry search_string, entries, confirm_even_if_one = false, entry_no = nil
|
67
|
-
# here we have a nuisance: we want the user to choose one entry
|
68
|
-
# by relative id (e.g. the third found), but we need to return
|
69
|
-
# the absolute id (to update the right entry in the safe)
|
70
|
-
#
|
71
|
-
# ... so we just keep track of the real ids with an array
|
72
|
-
# the relative id is the index in the array
|
73
|
-
|
74
|
-
found = Array.new
|
75
|
-
entries.each_with_index do |entry, index|
|
76
|
-
if entry["title"].downcase.include?(search_string.downcase)
|
77
|
-
found << index
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
if found.size == 0 then
|
82
|
-
printf "No entry matches the search criteria.\n"
|
83
|
-
exit -1
|
84
|
-
end
|
85
|
-
|
86
|
-
if entry_no then
|
87
|
-
# accept entry_no even if there is one entry
|
88
|
-
id = entry_no
|
89
|
-
elsif found.size > 1 or confirm_even_if_one then
|
90
|
-
# print the entry or the entries found together with their relative ids
|
91
|
-
found.each_with_index do |absolute_index, relative_index|
|
92
|
-
print_entry relative_index, entries[absolute_index]
|
93
|
-
end
|
94
|
-
|
95
|
-
printf "\nVarious matches." if found.size > 1
|
96
|
-
|
97
|
-
printf "\nSelect entry by ID (0..#{found.size-1}); -1 or empty string to exit: "
|
98
|
-
response = Readline.readline
|
99
|
-
id = response == "" ? -1 : response.to_i
|
100
|
-
while (id < -1 or id >= found.size)
|
101
|
-
response = Readline.readline "Select entry by ID (0..#{found.size-1}); -1 or empty string to exit: "
|
102
|
-
id = response == "" ? -1 : response.to_i
|
103
|
-
end
|
104
|
-
if id == -1 then
|
105
|
-
exit -1
|
106
|
-
end
|
107
|
-
else
|
108
|
-
id = 0
|
109
|
-
print_entry 0, entries[found[id]]
|
110
|
-
end
|
111
|
-
|
112
|
-
found[id]
|
113
|
-
end
|
114
|
-
|
115
|
-
#
|
116
|
-
# Print entry
|
117
|
-
#
|
118
|
-
def self.print_entry id, element
|
119
|
-
puts "\n---\nENTRY ID: #{id}"
|
120
|
-
# we need to duplicate, because deletion in place will remove
|
121
|
-
# passwords from entries (and, frankly, we need them)
|
122
|
-
new_el = element.dup
|
123
|
-
new_el.delete("password")
|
124
|
-
puts new_el.to_yaml
|
125
|
-
end
|
126
|
-
|
127
|
-
end
|
1
|
+
require 'pwss/generators/fields'
|
2
|
+
require 'pwss/generators/entry'
|
3
|
+
require 'pwss/generators/code'
|
4
|
+
require 'pwss/generators/bank_account'
|
5
|
+
require 'pwss/generators/credit_card'
|
6
|
+
require 'pwss/generators/software_license'
|
7
|
+
require 'pwss/generators/sim.rb'
|
8
|
+
|
9
|
+
require 'pwss/cipher'
|
10
|
+
require 'pwss/fileops'
|
11
|
+
require 'pwss/safe'
|
12
|
+
require 'pwss/password'
|
13
|
+
|
14
|
+
require 'pwss/cli/command_syntax'
|
15
|
+
require 'pwss/cli/command_semantics'
|
16
|
+
|
17
|
+
require 'pwss/version'
|
data/lib/pwss/cipher.rb
CHANGED
@@ -1,85 +1,22 @@
|
|
1
1
|
require 'encryptor'
|
2
|
-
|
2
|
+
|
3
3
|
#
|
4
4
|
# Cipher does encryption and decryption of data
|
5
5
|
# It reasons at the string level (both in input and in output)
|
6
6
|
#
|
7
|
-
module
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def self.decrypt string, password
|
13
|
-
begin
|
14
|
-
Encryptor.decrypt(:value => string, :key => password)
|
15
|
-
rescue Exception => e
|
16
|
-
puts "Unable to decrypt. Exiting"
|
17
|
-
exit 1
|
7
|
+
module Pwss
|
8
|
+
module Cipher
|
9
|
+
def self.encrypt string, password
|
10
|
+
Encryptor.encrypt(:value => string, :key => password)
|
18
11
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Ask for a password fom the command line
|
22
|
-
def self.ask_password prompt="Enter master password: "
|
23
|
-
printf prompt
|
24
|
-
system "stty -echo"
|
25
|
-
password = $stdin.gets.chomp
|
26
|
-
system "stty echo"
|
27
|
-
puts ""
|
28
|
-
password
|
29
|
-
end
|
30
|
-
|
31
|
-
# Ask for a password twice and make sure it is entered the same
|
32
|
-
def self.check_password prompt="master password"
|
33
|
-
match = false
|
34
|
-
while ! match
|
35
|
-
password = ask_password "Enter #{prompt}: "
|
36
|
-
repeat = ask_password "Repeat #{prompt}: "
|
37
|
-
match = (password == repeat)
|
38
12
|
|
39
|
-
|
40
|
-
|
13
|
+
def self.decrypt string, password
|
14
|
+
begin
|
15
|
+
Encryptor.decrypt(:value => string, :key => password)
|
16
|
+
rescue Exception => e
|
17
|
+
puts "Unable to decrypt. Exiting"
|
18
|
+
exit 1
|
41
19
|
end
|
42
20
|
end
|
43
|
-
password
|
44
21
|
end
|
45
|
-
|
46
|
-
# Ask for a password (twice) or generate one, if length is greater than 0
|
47
|
-
def self.check_or_generate prompt, length=0, alnum=false
|
48
|
-
length > 0 ? generate_password(length, alnum) : check_password(prompt)
|
49
|
-
end
|
50
|
-
|
51
|
-
#
|
52
|
-
# make the password available to the clipboard.
|
53
|
-
#
|
54
|
-
def self.password_to_clipboard password, counter = 30
|
55
|
-
old_clipboard = `pbpaste`
|
56
|
-
system("printf \"%s\" \"#{password}\" | pbcopy")
|
57
|
-
|
58
|
-
begin
|
59
|
-
if counter <= 0
|
60
|
-
STDIN.flush
|
61
|
-
puts "\nPassword available in clipboard: press enter when you are done."
|
62
|
-
STDIN.getc
|
63
|
-
else
|
64
|
-
puts "\nPassword available in clipboard for #{counter} seconds."
|
65
|
-
sleep(counter)
|
66
|
-
end
|
67
|
-
system("printf \"#{old_clipboard}\" | pbcopy")
|
68
|
-
rescue Exception => e
|
69
|
-
system("printf \"#{old_clipboard}\" | pbcopy")
|
70
|
-
puts "Clipboard restored. Exiting."
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
# Generate a random password
|
77
|
-
# (Adapted from: http://randompasswordsgenerator.net/tutorials/ruby-random-password-generator.html)
|
78
|
-
def self.generate_password(length=8, alnum=false)
|
79
|
-
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ1234567890'
|
80
|
-
chars += '!@#$%^&*()_+=[]{}<>/~,.;:|' if not alnum
|
81
|
-
Array.new(length) { chars[rand(chars.length)].chr }.join
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
22
|
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require 'pwss/version'
|
2
|
+
|
3
|
+
module Pwss
|
4
|
+
# what we are supposed to do with each command
|
5
|
+
module CommandSemantics
|
6
|
+
VERSION = Pwss::VERSION
|
7
|
+
# TODO: make the list of entries read from code
|
8
|
+
MAN = <<EOS
|
9
|
+
NAME
|
10
|
+
pwss -- A command-line password manager
|
11
|
+
|
12
|
+
SYNOPSYS
|
13
|
+
pwss command [options] [args]
|
14
|
+
|
15
|
+
DESCRIPTION
|
16
|
+
PWSS is a password manager, in the spirit of pass and pws.
|
17
|
+
|
18
|
+
Features:
|
19
|
+
|
20
|
+
* PWSS manages password *files*:
|
21
|
+
- A password file can store different entries (password and other
|
22
|
+
sensitive information)
|
23
|
+
- The user can manage different password files (e.g., work, personal)
|
24
|
+
|
25
|
+
* Entries in a password file can be of different types. Each type stores
|
26
|
+
different information. Use the 'describe' command for more info about
|
27
|
+
the available types and their fields.
|
28
|
+
|
29
|
+
* Password files can be encrypted
|
30
|
+
|
31
|
+
* Encrypted password files can be decrypted, for instance, to batch process
|
32
|
+
entries, to migrate to another tool, or to manually edit entries
|
33
|
+
|
34
|
+
* Entries are human-readable (and editable), when the password file is not
|
35
|
+
encrypted
|
36
|
+
|
37
|
+
* A console allows to decrypt a file once and perform multiple queries
|
38
|
+
|
39
|
+
EXAMPLES
|
40
|
+
pwss help # get syntax of each command
|
41
|
+
|
42
|
+
# scenario
|
43
|
+
pwss init -f a.enc # generate an encrypted safe a.enc
|
44
|
+
pwss add -f a.enc # add an entry (pwss will generate a random 16-char password)
|
45
|
+
pwss get -f a.enc my secret account # find an entry
|
46
|
+
pwss console -f a.enc # decrypt a.enc and enter the pwss console to operate on a.enc
|
47
|
+
|
48
|
+
VERSION
|
49
|
+
This is version #{VERSION}
|
50
|
+
|
51
|
+
LICENSE
|
52
|
+
MIT
|
53
|
+
|
54
|
+
SEE ALSO
|
55
|
+
pwss man
|
56
|
+
pwss help
|
57
|
+
https://github.com/avillafiorita/pwss
|
58
|
+
EOS
|
59
|
+
|
60
|
+
# the default filename
|
61
|
+
# YOU SHOULDN'T BE USING THESE CONTANSTS. USE `default_filename` INSTEAD
|
62
|
+
DEFAULT_BASENAME = File.join(Dir.home, ".pwss.yaml")
|
63
|
+
DEFAULT_FILENAME = DEFAULT_BASENAME + ".gpg"
|
64
|
+
|
65
|
+
# return the default filename
|
66
|
+
#
|
67
|
+
# this is obtained by looking for plain text or encryped versions
|
68
|
+
# of the DEFAULT_BASENAME, with the following priority: .enc,
|
69
|
+
# .gpg, plain text.
|
70
|
+
#
|
71
|
+
# If no file is found (like it might be the case when running the
|
72
|
+
# init command), use GPG
|
73
|
+
def self.default_filename
|
74
|
+
[".enc", ".gpg", ""].each do |ext|
|
75
|
+
filename = DEFAULT_BASENAME + ext
|
76
|
+
return filename if File.exist?(filename)
|
77
|
+
end
|
78
|
+
return DEFAULT_FILENAME
|
79
|
+
end
|
80
|
+
|
81
|
+
# return true if the default basename appears with different
|
82
|
+
# extensions.
|
83
|
+
#
|
84
|
+
# for instance: if the DEFAULT_BASENAME appears both with .gpg and .enc
|
85
|
+
# (or plain and encrypted).
|
86
|
+
#
|
87
|
+
# This is potentially a problem, since all operations are
|
88
|
+
# performed on a different file from the one the user believes it
|
89
|
+
# is operating on.
|
90
|
+
def self.ambiguous_default
|
91
|
+
[".enc", ".gpg", ""].map { |ext| File.exist?(DEFAULT_BASENAME + ext) }.count(true) > 1
|
92
|
+
end
|
93
|
+
|
94
|
+
# return true if none of the default files exist
|
95
|
+
def self.no_default
|
96
|
+
[".enc", ".gpg", ""].map { |ext| File.exist?(DEFAULT_BASENAME + ext) }.count(true) == 0
|
97
|
+
end
|
98
|
+
|
99
|
+
# return all the default safes we look for
|
100
|
+
def self.all_safes
|
101
|
+
[".enc", ".gpg", ""].map { |ext| DEFAULT_BASENAME + ext }
|
102
|
+
end
|
103
|
+
|
104
|
+
# return the existing safes
|
105
|
+
def self.existing_safes
|
106
|
+
[".enc", ".gpg", ""].map { |ext| DEFAULT_BASENAME + ext }.select { |x| File.exist?(x) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# cache is the content of the file last operated on
|
110
|
+
# is it used
|
111
|
+
@@cache = nil
|
112
|
+
|
113
|
+
def self.version opts = nil, argv = []
|
114
|
+
puts "pwss version #{VERSION}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.man opts = nil, argv = []
|
118
|
+
puts MAN
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.help opts = nil, argv = []
|
122
|
+
all_commands = Pwss::CommandSyntax.commands
|
123
|
+
|
124
|
+
if argv != []
|
125
|
+
argv.map { |x| puts all_commands[x.to_sym][0] }
|
126
|
+
else
|
127
|
+
puts "pwss command [options] [args]"
|
128
|
+
puts ""
|
129
|
+
puts "Available commands:"
|
130
|
+
puts ""
|
131
|
+
all_commands.keys.each do |key|
|
132
|
+
puts " " + all_commands[key][0].banner
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.describe opts = nil, argv = []
|
138
|
+
if opts[:type]
|
139
|
+
types = [("Pwss::" + opts[:type].capitalize).to_sym]
|
140
|
+
else
|
141
|
+
types = [Pwss::Entry] + ObjectSpace.each_object(Class).select { |klass| klass < Pwss::Entry }
|
142
|
+
end
|
143
|
+
types.each do |type|
|
144
|
+
t = eval("#{type}.new")
|
145
|
+
puts "#{type.to_s.gsub("Pwss::", "").downcase}:\n #{t.fields.join(", ")}\n\n"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.init opts, argv = []
|
150
|
+
filename = opts[:filename] || @@cache.filename || DEFAULT_FILENAME
|
151
|
+
|
152
|
+
if File.exist?(filename)
|
153
|
+
raise "Error: file #{filename} already exists."
|
154
|
+
end
|
155
|
+
|
156
|
+
safe = Pwss::Safe.new filename
|
157
|
+
safe.save
|
158
|
+
|
159
|
+
puts "New safe created in #{filename}"
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.list opts, argv = []
|
163
|
+
safe = use_safe opts[:filename]
|
164
|
+
clean = opts[:clean]
|
165
|
+
|
166
|
+
cleaned_entries = safe.prune(["created_at", "updated_at"]).map { |x| Pwss::Fields.to_clean_hash x }
|
167
|
+
puts cleaned_entries.to_yaml
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.get opts, argv
|
171
|
+
waiting = opts[:wait]
|
172
|
+
stdout_opt = opts[:stdout]
|
173
|
+
field_name = opts[:field] || "password"
|
174
|
+
hide = opts[:hide]
|
175
|
+
string = argv.join(" ")
|
176
|
+
|
177
|
+
safe = use_safe opts[:filename]
|
178
|
+
entries_with_idx = safe.match string
|
179
|
+
id = Pwss::Safe.choose_entry entries_with_idx
|
180
|
+
if id != -1 then
|
181
|
+
puts (hide ? safe.get_pruned(id).to_yaml : safe.get(id).to_yaml )
|
182
|
+
field_value = safe.get_field id, field_name
|
183
|
+
if field_value then
|
184
|
+
stdout_opt ? printf("%s", field_value) : Pwss::Password.to_clipboard(field_name, field_value, waiting)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.add_entry opts, argv
|
190
|
+
waiting = opts[:wait]
|
191
|
+
type = opts[:type] || "entry"
|
192
|
+
strategy = opts[:ask] ? "ask" : (opts[:method] || "random")
|
193
|
+
length = opts[:length]
|
194
|
+
|
195
|
+
safe = use_safe opts[:filename]
|
196
|
+
|
197
|
+
# the title can be specified in the argument
|
198
|
+
arguments = Hash.new
|
199
|
+
arguments["title"] = argv.join(" ") if argv != []
|
200
|
+
arguments[:strategy] = strategy
|
201
|
+
arguments[:length] = length
|
202
|
+
|
203
|
+
new_entry = eval("Pwss::" + type.capitalize).new
|
204
|
+
new_entry.ask arguments
|
205
|
+
|
206
|
+
puts "Adding entry '#{new_entry.entry["title"]}' of type '#{type}' to #{safe.filename}"
|
207
|
+
safe.add new_entry.entry
|
208
|
+
safe.save
|
209
|
+
puts "Entry added"
|
210
|
+
|
211
|
+
# make password available in the clipboard, if there is a password to make available
|
212
|
+
if new_entry.entry["password"]
|
213
|
+
Pwss::Password.to_clipboard "password", new_entry.entry["password"], waiting
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.update opts, argv
|
218
|
+
field = (opts.to_hash[:password] or opts.to_hash[:method] or opts.to_hash[:ask]) ? "password" : opts.to_hash[:field]
|
219
|
+
strategy = opts.to_hash[:ask] ? "ask" : (opts.to_hash[:method] || "random")
|
220
|
+
length = opts.to_hash[:length]
|
221
|
+
waiting = opts.to_hash[:wait]
|
222
|
+
string = argv.join(" ") # the entry we are looking for
|
223
|
+
|
224
|
+
if not field then
|
225
|
+
raise "Error: please specify a field to update (use --field, -p, or --ask)"
|
226
|
+
end
|
227
|
+
|
228
|
+
safe = use_safe opts[:filename]
|
229
|
+
|
230
|
+
entries_with_idx = safe.match string
|
231
|
+
id = Pwss::Safe.choose_entry entries_with_idx, true
|
232
|
+
if id != -1 then
|
233
|
+
field_value = Pwss::Fields.ask field, { strategy: strategy, length: length }
|
234
|
+
puts "Updating #{field} field of '#{safe.entries[id]["title"]}' in #{safe.filename}"
|
235
|
+
safe.update id, field, field_value
|
236
|
+
safe.save
|
237
|
+
puts "Entry updated"
|
238
|
+
|
239
|
+
# make the field available in the clipboard, just in case it is needed
|
240
|
+
if field == "password"
|
241
|
+
Pwss::Password.to_clipboard "password", field_value, waiting
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.destroy opts, argv
|
247
|
+
safe = use_safe opts[:filename]
|
248
|
+
|
249
|
+
string = argv.join(" ")
|
250
|
+
entries_with_idx = safe.match string
|
251
|
+
id = Pwss::Safe.choose_entry entries_with_idx, true
|
252
|
+
if id != -1 then
|
253
|
+
safe.destroy id
|
254
|
+
safe.save
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.encrypt opts, argv = []
|
259
|
+
# filename: use the one passed from the cli or the cached one or .pwss.yaml DEFAULT_*BASE*NAME
|
260
|
+
filename = opts[:filename] || (@@cache ? @@cache.filename : DEFAULT_BASENAME)
|
261
|
+
encryption = opts[:symmetric] ? :enc : :gpg
|
262
|
+
|
263
|
+
if not File.exist?(filename)
|
264
|
+
raise "Error: file #{filename} does not exist."
|
265
|
+
end
|
266
|
+
|
267
|
+
if Pwss::FileOps.encrypted? filename
|
268
|
+
raise "Error: #{filename} ends with '.gpg' or '.enc' (and I assume these files to be encrypted)"
|
269
|
+
end
|
270
|
+
|
271
|
+
if encryption == :enc then
|
272
|
+
password = Pwss::Password.ask_password_twice
|
273
|
+
if password == "" then
|
274
|
+
raise "Error: Please specify a non-empty password."
|
275
|
+
end
|
276
|
+
else
|
277
|
+
password = nil # it will be asked by GPG
|
278
|
+
end
|
279
|
+
|
280
|
+
safe = use_safe filename
|
281
|
+
safe.toggle_encryption :password => password, :schema => encryption
|
282
|
+
safe.save
|
283
|
+
puts "An encrypted copy now lives in #{safe.filename}"
|
284
|
+
puts "You might want to check everything is ok and delete the plain file: #{filename}"
|
285
|
+
puts "If you do nothing, the next pwss command will run on #{default_filename}"
|
286
|
+
end
|
287
|
+
|
288
|
+
def self.decrypt opts, argv = []
|
289
|
+
# filename: passed from options, cached one or, in order, .gpg, .enc, plain (but plain will fail)
|
290
|
+
filename = opts[:filename] || (@@cache ? @@cache.filename : default_filename)
|
291
|
+
|
292
|
+
if not File.exist?(filename)
|
293
|
+
raise "Error: file #{filename} does not exist."
|
294
|
+
end
|
295
|
+
|
296
|
+
if not Pwss::FileOps.encrypted? filename
|
297
|
+
raise "Error: #{filename} does not end with '.gpg' or '.enc' (and I assume it to be in plain text)"
|
298
|
+
end
|
299
|
+
|
300
|
+
safe = use_safe filename
|
301
|
+
safe.toggle_encryption
|
302
|
+
safe.save
|
303
|
+
puts "A plain text copy now lives in #{safe.filename}"
|
304
|
+
puts "You might want to check everything is ok and delete the plain file: #{filename}"
|
305
|
+
puts "If you do nothing, the next pwss command will run on #{default_filename}"
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.console opts, argv = []
|
309
|
+
all_commands = Pwss::CommandSyntax.commands
|
310
|
+
all_commands.delete(:console)
|
311
|
+
open opts, argv # open and cache the file
|
312
|
+
|
313
|
+
i = 0
|
314
|
+
while true
|
315
|
+
string = Readline.readline('pwss:%03d> ' % i, true)
|
316
|
+
string.gsub!(/^pwss /, "") # as a courtesy, remove any leading pwss string
|
317
|
+
if string == "exit" or string == "quit" or string == "." then
|
318
|
+
exit 0
|
319
|
+
end
|
320
|
+
reps all_commands, string.split(' ')
|
321
|
+
i = i + 1
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.open opts, argv = []
|
326
|
+
filename = opts[:filename] || default_filename
|
327
|
+
@@cache = load_safe filename
|
328
|
+
puts "Loaded #{filename}"
|
329
|
+
end
|
330
|
+
|
331
|
+
def self.default opts, argv = []
|
332
|
+
if @@cache
|
333
|
+
@@cache.filename
|
334
|
+
elsif self.no_default
|
335
|
+
puts "No default password file found."
|
336
|
+
puts "Use -f if you have a password file stored somewhere else."
|
337
|
+
puts "pwss init will create #{default_filename}."
|
338
|
+
elsif self.ambiguous_default
|
339
|
+
puts "Operating on #{default_filename}."
|
340
|
+
puts "Warning: #{existing_safes.join(", ")} exist."
|
341
|
+
else
|
342
|
+
puts "Operating on #{default_filename}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# read-eval-print step
|
347
|
+
def self.reps all_commands, argv
|
348
|
+
if argv == []
|
349
|
+
Pwss::CommandSemantics.help
|
350
|
+
exit 0
|
351
|
+
else
|
352
|
+
command = argv[0]
|
353
|
+
syntax_and_semantics = all_commands[command.to_sym]
|
354
|
+
|
355
|
+
if syntax_and_semantics
|
356
|
+
opts = syntax_and_semantics[0]
|
357
|
+
function = syntax_and_semantics[1]
|
358
|
+
|
359
|
+
begin
|
360
|
+
parser = Slop::Parser.new(opts)
|
361
|
+
result = parser.parse(argv[1..-1])
|
362
|
+
options = result.to_hash
|
363
|
+
arguments = result.arguments
|
364
|
+
|
365
|
+
eval "Pwss::CommandSemantics::#{function}(options, arguments)"
|
366
|
+
rescue Slop::Error => e
|
367
|
+
puts "pwss: #{e}"
|
368
|
+
rescue Exception => e
|
369
|
+
puts e
|
370
|
+
end
|
371
|
+
else
|
372
|
+
puts "pwss: '#{command}' is not a pwss command. See 'pwss help'"
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
private
|
378
|
+
|
379
|
+
# use a specific filename (if specified), try @@cache if -f is not specified, or the default filename
|
380
|
+
def self.use_safe filename
|
381
|
+
if filename then
|
382
|
+
load_safe filename
|
383
|
+
elsif @@cache then
|
384
|
+
@@cache
|
385
|
+
else
|
386
|
+
load_safe default_filename
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# load a password safe
|
391
|
+
def self.load_safe filename
|
392
|
+
if File.exist?(filename)
|
393
|
+
safe = Pwss::Safe.new filename
|
394
|
+
safe.load
|
395
|
+
safe
|
396
|
+
else
|
397
|
+
raise "Error: file #{filename} does not exist."
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
end
|