osp 0.1.0.pre.dev.1
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 +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +28 -0
- data/Makefile +8 -0
- data/Makefile.common +51 -0
- data/README.md +20 -0
- data/bin/catmp +56 -0
- data/bin/osp +384 -0
- data/lib/ext/string.rb +8 -0
- data/lib/osp/host.rb +146 -0
- data/lib/osp/osp.rb +183 -0
- data/lib/osp/version.rb +9 -0
- data/lib/osp.rb +6 -0
- data/osp.gemspec +36 -0
- data/osp.sublime-project +10 -0
- data/tests/tc_host.rb +120 -0
- data/tests/tc_osp.rb +137 -0
- data/tests/ts_all.rb +4 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 91fa1ed62524c4d5a625fc41b705e53ec07c1025
|
4
|
+
data.tar.gz: 4953a9cc50c9ae298912a8829ad040caa0a273e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a3b038055e9eabb2b95739973349a846db72e4dab13fb75d02eac627284acd91e4d640804edc42158ddad3933cbe80f272d5a57e7308280a15cad09aa7edc188
|
7
|
+
data.tar.gz: 10ed0c220116da7cb8f1b4d72d5dcdd27e9eafb55a8a222032e82e22bc673c7ac3b66263215337dc37caf58c68dd19a63ed66025a015c082407c1b630d019fe9
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
osp (0.1.0.pre.dev.1)
|
5
|
+
highline (~> 1.7)
|
6
|
+
msgpack (~> 0.7)
|
7
|
+
rainbow (~> 2.0)
|
8
|
+
thefox-ext (~> 1.2)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
highline (1.7.8)
|
14
|
+
minitest (5.8.3)
|
15
|
+
msgpack (0.7.1)
|
16
|
+
rainbow (2.0.0)
|
17
|
+
thefox-ext (1.2.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
bundler (~> 1.10)
|
24
|
+
minitest (~> 5.8)
|
25
|
+
osp!
|
26
|
+
|
27
|
+
BUNDLED WITH
|
28
|
+
1.10.2
|
data/Makefile
ADDED
data/Makefile.common
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
|
2
|
+
# Ruby Common Big
|
3
|
+
# 2015-12-15
|
4
|
+
|
5
|
+
MV = mv -nv
|
6
|
+
RM = rm -rf
|
7
|
+
MKDIR = mkdir -p
|
8
|
+
BUNDLER = bundle
|
9
|
+
BUNDLER_OPTIONS = --jobs=5 --retry=3
|
10
|
+
GEMSPEC_FILE = $(GEM_NAME).gemspec
|
11
|
+
|
12
|
+
.PHONY: all $(ALL_TARGETS_EXT)
|
13
|
+
all: setup
|
14
|
+
|
15
|
+
.PHONY: setup
|
16
|
+
setup: .setup
|
17
|
+
|
18
|
+
.setup:
|
19
|
+
$(BUNDLER) install $(BUNDLER_OPTIONS)
|
20
|
+
touch $@
|
21
|
+
|
22
|
+
.PHONY: install
|
23
|
+
install:
|
24
|
+
gem_file=$$(gem build $(GEMSPEC_FILE) | grep 'File:' | tail -1 | awk '{ print $$2 }'); \
|
25
|
+
sudo gem install $$gem_file; \
|
26
|
+
$(RM) $$gem_file
|
27
|
+
|
28
|
+
.PHONY: uninstall
|
29
|
+
uninstall:
|
30
|
+
sudo gem uninstall $(GEM_NAME)
|
31
|
+
|
32
|
+
.PHONY: update
|
33
|
+
update:
|
34
|
+
$(BUNDLER) update
|
35
|
+
|
36
|
+
.PHONY: clean
|
37
|
+
clean:
|
38
|
+
$(RM) .setup
|
39
|
+
|
40
|
+
.PHONY: release
|
41
|
+
release: | releases
|
42
|
+
set -e; \
|
43
|
+
gem_file=$$(gem build $(GEMSPEC_FILE) | grep 'File:' | tail -1 | awk '{ print $$2 }'); \
|
44
|
+
dst="releases/$$gem_file"; \
|
45
|
+
[ ! -f $$dst ]; \
|
46
|
+
$(MV) $$gem_file releases; \
|
47
|
+
gem push $$dst; \
|
48
|
+
echo 'done'
|
49
|
+
|
50
|
+
releases:
|
51
|
+
$(MKDIR) $@
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# One Shall Pass for Command Line
|
2
|
+
|
3
|
+
## Project Links
|
4
|
+
|
5
|
+
- [Gem](https://rubygems.org/gems/osp)
|
6
|
+
- [Travis CI Repository](https://travis-ci.org/TheFox/osp)
|
7
|
+
|
8
|
+
## Weblinks
|
9
|
+
|
10
|
+
- [maxtaco/oneshallpass](https://github.com/maxtaco/oneshallpass)
|
11
|
+
- [Ruby OpenSSL HMAC](http://ruby-doc.org/stdlib-2.2.4/libdoc/openssl/rdoc/OpenSSL/HMAC.html)
|
12
|
+
- [Ruby OpenSSL PKCS5](http://ruby-doc.org/stdlib-2.2.2/libdoc/openssl/rdoc/OpenSSL/PKCS5.html)
|
13
|
+
- [Ruby AES Encryption using OpenSSL](https://gist.github.com/byu/99651)
|
14
|
+
|
15
|
+
## License
|
16
|
+
Copyright (C) 2015 Christian Mayer <http://fox21.at>
|
17
|
+
|
18
|
+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
19
|
+
|
20
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/bin/catmp
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: UTF-8
|
3
|
+
|
4
|
+
raise 'Ruby >=2.1 required' unless RUBY_VERSION >= '2.1.0'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'yaml'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'msgpack'
|
12
|
+
|
13
|
+
|
14
|
+
options = {
|
15
|
+
:path => nil,
|
16
|
+
:yaml => false,
|
17
|
+
}
|
18
|
+
opts = OptionParser.new do |opts|
|
19
|
+
opts.banner = 'Usage: catmp'
|
20
|
+
opts.separator('')
|
21
|
+
|
22
|
+
# opts.on('-p', '--path <path>', 'Path msgpack file.') do |path|
|
23
|
+
# options[:path] = path
|
24
|
+
# end
|
25
|
+
|
26
|
+
opts.on('-y', '--yaml', 'Output in YAML format.') do
|
27
|
+
options[:yaml] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on_tail('-h', '--help', 'Show this message.') do
|
31
|
+
puts opts
|
32
|
+
puts
|
33
|
+
exit 3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
#ARGV << '-h' if ARGV.count == 0
|
37
|
+
args = opts.parse(ARGV)
|
38
|
+
|
39
|
+
if args.count > 0
|
40
|
+
options[:path] = args.shift
|
41
|
+
|
42
|
+
if !options[:path].nil?
|
43
|
+
ar = MessagePack.unpack(File.binread(options[:path]))
|
44
|
+
|
45
|
+
if options[:yaml]
|
46
|
+
print YAML.dump(ar)
|
47
|
+
else
|
48
|
+
pp ar
|
49
|
+
end
|
50
|
+
else
|
51
|
+
raise 'FATAL ERROR: path invalid.'
|
52
|
+
end
|
53
|
+
else
|
54
|
+
opts.parse(['-h'])
|
55
|
+
end
|
56
|
+
|
data/bin/osp
ADDED
@@ -0,0 +1,384 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: UTF-8
|
3
|
+
|
4
|
+
raise 'Ruby >=2.1 required' unless RUBY_VERSION >= '2.1.0'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'fileutils'
|
8
|
+
#require 'securerandom'
|
9
|
+
#require 'openssl'
|
10
|
+
#require 'base64'
|
11
|
+
# require 'yaml/store'
|
12
|
+
require 'pp'
|
13
|
+
|
14
|
+
require 'bundler/setup'
|
15
|
+
#require 'highline/import'
|
16
|
+
require 'highline'
|
17
|
+
require 'rainbow'
|
18
|
+
require 'msgpack'
|
19
|
+
require 'osp'
|
20
|
+
|
21
|
+
#msg = [1,2,3].to_msgpack
|
22
|
+
#MessagePack.unpack(msg)
|
23
|
+
|
24
|
+
|
25
|
+
options = {
|
26
|
+
:database_path => "#{Dir.home}/.osp",
|
27
|
+
}
|
28
|
+
opts = OptionParser.new do |opts|
|
29
|
+
opts.banner = 'Usage: osp'
|
30
|
+
opts.separator('')
|
31
|
+
|
32
|
+
opts.on('-d', '--database <path>', 'Path to the database file.') do |path|
|
33
|
+
options[:database_path] = path
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on_tail('-h', '--help', 'Show this message.') do
|
37
|
+
puts opts
|
38
|
+
puts
|
39
|
+
exit 3
|
40
|
+
end
|
41
|
+
end
|
42
|
+
#ARGV << '-h' if ARGV.count == 0
|
43
|
+
args = opts.parse(ARGV)
|
44
|
+
|
45
|
+
|
46
|
+
@cli = HighLine.new
|
47
|
+
|
48
|
+
puts "OSP - OneShallPass #{TheFox::OSP::VERSION}"
|
49
|
+
puts TheFox::OSP::HOMEPAGE
|
50
|
+
puts
|
51
|
+
|
52
|
+
puts 'Master Login'
|
53
|
+
|
54
|
+
# email = @cli.ask(' Email: ')
|
55
|
+
puts ' Email: xyz@example.com'
|
56
|
+
# password = @cli.ask('Password: '){ |q| q.echo = '*' }
|
57
|
+
puts 'Password: xyz'
|
58
|
+
|
59
|
+
Console.cursor_jump_to_column
|
60
|
+
Console.cursor_up(2)
|
61
|
+
Console.screen_clear_to_bottom
|
62
|
+
|
63
|
+
puts ' Email: *****'
|
64
|
+
puts 'Password: *****'
|
65
|
+
puts
|
66
|
+
|
67
|
+
email = 'xyz@example.com'
|
68
|
+
password = 'xyz'
|
69
|
+
|
70
|
+
database = {
|
71
|
+
'meta' => {
|
72
|
+
'version' => 1,
|
73
|
+
'created_at' => DateTime.now.to_s,
|
74
|
+
'updated_at' => DateTime.now.to_s,
|
75
|
+
},
|
76
|
+
'hosts' => {}
|
77
|
+
}
|
78
|
+
has_database = false
|
79
|
+
has_database_changes = false
|
80
|
+
if File.exist?(options[:database_path])
|
81
|
+
puts "Found database file: #{options[:database_path]}",
|
82
|
+
'Try to open it ...'
|
83
|
+
|
84
|
+
begin
|
85
|
+
database = MessagePack.unpack(File.binread(options[:database_path]))
|
86
|
+
database['hosts'] = database['hosts'].map{ |name, host| [name, TheFox::OSP::Host.from_h(host)] }.to_h
|
87
|
+
rescue Exception => e
|
88
|
+
puts "FATAL ERROR: couldn't open database:"
|
89
|
+
puts "'#{e}'"
|
90
|
+
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
printf "Calculating base hash: #{TheFox::OSP::HASHES_N} (#{TheFox::OSP::HASHES_EXP}-bit) - please wait ..."
|
96
|
+
|
97
|
+
osp = TheFox::OSP::OSP.new(email, password, TheFox::OSP::HASHES_N)
|
98
|
+
osp.key_derivation
|
99
|
+
puts ' done'
|
100
|
+
|
101
|
+
database['hosts'].each{ |name, host| host.osp = osp }
|
102
|
+
|
103
|
+
def password_callback_method(step, pw)
|
104
|
+
printf '.'
|
105
|
+
end
|
106
|
+
osp.password_callback_method = self.method('password_callback_method')
|
107
|
+
|
108
|
+
def host_show(host, regenerate_password = false)
|
109
|
+
if !host.has_generated_password? || regenerate_password
|
110
|
+
print 'Generate password '
|
111
|
+
host.generate_password(regenerate_password)
|
112
|
+
puts ' done'
|
113
|
+
end
|
114
|
+
|
115
|
+
puts
|
116
|
+
puts " Name: #{host.name}"
|
117
|
+
puts "Generation: #{host.generation}"
|
118
|
+
puts " Length: #{host.length}"
|
119
|
+
puts " Symbols: #{host.symbols}"
|
120
|
+
puts " Hashes: #{host.hashes}"
|
121
|
+
puts " Password: #{host.password}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def host_edit(osp, host = nil)
|
125
|
+
host = TheFox::OSP::Host.new(osp) if host.nil?
|
126
|
+
|
127
|
+
tmp = @cli.ask(' Name: ' + (!host.name.nil? ? "[#{host.name}]" : '') + ' ').strip.to_s
|
128
|
+
# puts "name: '#{tmp}' #{tmp.class}"
|
129
|
+
|
130
|
+
host.name = tmp if tmp != ''
|
131
|
+
# puts "name: '#{host.name}' #{host.name.class}"
|
132
|
+
|
133
|
+
host.name = nil if host.name == ''
|
134
|
+
# puts "name: '#{host.name}' #{host.name.class}"
|
135
|
+
|
136
|
+
if host.name.nil?
|
137
|
+
# puts "No host input."
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
|
141
|
+
host.generation = @cli.ask("Generation: [#{host.generation}] ", Integer){ |q|
|
142
|
+
q.default = host.generation
|
143
|
+
q.in = 1..99 }.to_i
|
144
|
+
|
145
|
+
host.length = @cli.ask(" Length: [#{host.length}] ", Integer){ |q|
|
146
|
+
q.default = host.length
|
147
|
+
q.in = TheFox::OSP::PASSWORD_MIN_SIZE..TheFox::OSP::PASSWORD_MAX_SIZE }.to_i
|
148
|
+
|
149
|
+
host.symbols = @cli.ask(" Symbols: [#{host.symbols}] ", Integer){ |q|
|
150
|
+
q.default = host.symbols
|
151
|
+
q.in = 0..3 }.to_i
|
152
|
+
|
153
|
+
host.updated_at = DateTime.now
|
154
|
+
|
155
|
+
host
|
156
|
+
end
|
157
|
+
|
158
|
+
actions = ['begin']
|
159
|
+
while true
|
160
|
+
case actions.pop
|
161
|
+
when 'begin'
|
162
|
+
puts
|
163
|
+
puts "Type '?' for help."
|
164
|
+
when 'n'
|
165
|
+
puts
|
166
|
+
host = host_edit(osp)
|
167
|
+
if !host.nil?
|
168
|
+
host_show(host)
|
169
|
+
puts
|
170
|
+
|
171
|
+
add = @cli.ask('Add to database? [yN] ', String){ |q| q.character = true }.strip.downcase
|
172
|
+
add = 'n' if add == ''
|
173
|
+
puts "Answer: '#{add}'"
|
174
|
+
|
175
|
+
if add == 'y'
|
176
|
+
has_database_changes = true
|
177
|
+
|
178
|
+
database['hosts'][host.name] = host
|
179
|
+
end
|
180
|
+
else
|
181
|
+
puts "ERROR: hostname can't be nothing."
|
182
|
+
end
|
183
|
+
when 'l'
|
184
|
+
puts
|
185
|
+
puts 'List Hosts'
|
186
|
+
puts
|
187
|
+
|
188
|
+
hosts_n = database['hosts'].count
|
189
|
+
format = '%' + hosts_n.to_s.length.to_s + 'd'
|
190
|
+
|
191
|
+
# puts "format: '#{format}'"
|
192
|
+
|
193
|
+
n = 0
|
194
|
+
database['hosts'].values.each do |host|
|
195
|
+
n += 1
|
196
|
+
# pp host
|
197
|
+
printf "#{format}. %s\n", n, host.name
|
198
|
+
end
|
199
|
+
when 's'
|
200
|
+
found_hosts = []
|
201
|
+
|
202
|
+
search_actions = ['begin']
|
203
|
+
while true
|
204
|
+
case search_actions.pop
|
205
|
+
when 'begin'
|
206
|
+
puts
|
207
|
+
puts 'Entering search submenu.'
|
208
|
+
|
209
|
+
search_actions << 's'
|
210
|
+
when 's'
|
211
|
+
puts
|
212
|
+
|
213
|
+
host_name = @cli.ask('Search host names: ', String).strip.downcase
|
214
|
+
re = Regexp.new(host_name, Regexp::IGNORECASE)
|
215
|
+
|
216
|
+
found_hosts = database['hosts'].select{ |name, host| re.match(host.name) }
|
217
|
+
|
218
|
+
search_actions << 'l'
|
219
|
+
when 'l'
|
220
|
+
puts
|
221
|
+
if found_hosts.count > 0
|
222
|
+
puts "#{found_hosts.count} host(s) found:"
|
223
|
+
|
224
|
+
hosts_n = found_hosts.count
|
225
|
+
format = '%' + hosts_n.to_s.length.to_s + 'd'
|
226
|
+
n = 0
|
227
|
+
found_hosts.each do |name, host|
|
228
|
+
n += 1
|
229
|
+
printf "#{format}. %s\n", n, host.name
|
230
|
+
end
|
231
|
+
else
|
232
|
+
puts 'No hosts found.'
|
233
|
+
end
|
234
|
+
when 'i'
|
235
|
+
puts
|
236
|
+
if found_hosts.count > 0
|
237
|
+
index = @cli.ask('Show host, select by number: ', Integer){ |q|
|
238
|
+
q.character = true if found_hosts.count < 10
|
239
|
+
q.in = 1..(found_hosts.count) }.to_i
|
240
|
+
|
241
|
+
puts
|
242
|
+
puts "Host ##{index} selected:"
|
243
|
+
index -= 1
|
244
|
+
selected_host = nil
|
245
|
+
begin
|
246
|
+
selected_host = found_hosts.values[index]
|
247
|
+
rescue Exception => e
|
248
|
+
puts "ERROR: #{e}"
|
249
|
+
end
|
250
|
+
|
251
|
+
if !selected_host.nil?
|
252
|
+
host_show(selected_host)
|
253
|
+
end
|
254
|
+
else
|
255
|
+
puts 'No hosts found.'
|
256
|
+
end
|
257
|
+
when 'e'
|
258
|
+
puts
|
259
|
+
if found_hosts.count > 0
|
260
|
+
index = @cli.ask('Edit, select host by number: ', Integer){ |q|
|
261
|
+
q.character = true if found_hosts.count < 10
|
262
|
+
q.in = 1..(found_hosts.count) }.to_i
|
263
|
+
|
264
|
+
puts
|
265
|
+
puts "Host ##{index} selected:"
|
266
|
+
index -= 1
|
267
|
+
selected_host = nil
|
268
|
+
begin
|
269
|
+
selected_host = found_hosts.values[index].clone
|
270
|
+
rescue Exception => e
|
271
|
+
puts "ERROR: #{e}"
|
272
|
+
end
|
273
|
+
|
274
|
+
if !selected_host.nil?
|
275
|
+
host_edit(osp, selected_host)
|
276
|
+
host_show(selected_host)
|
277
|
+
|
278
|
+
database['hosts'][selected_host.name] = selected_host
|
279
|
+
|
280
|
+
has_database_changes = true
|
281
|
+
end
|
282
|
+
else
|
283
|
+
puts 'No hosts found.'
|
284
|
+
end
|
285
|
+
# when 'd'
|
286
|
+
# puts
|
287
|
+
# if found_hosts.count > 0
|
288
|
+
# puts 'Delete'
|
289
|
+
# puts
|
290
|
+
# else
|
291
|
+
# puts 'No hosts found.'
|
292
|
+
# end
|
293
|
+
when 'q'
|
294
|
+
break
|
295
|
+
when '?'
|
296
|
+
puts
|
297
|
+
puts 'Search-menu help:'
|
298
|
+
puts "\ts - New search."
|
299
|
+
puts "\tl - List found hosts."
|
300
|
+
puts "\ti - Print informations about a host found by a search."
|
301
|
+
puts "\te - Edit a host found by search."
|
302
|
+
# puts "\td - Delete a host found by search."
|
303
|
+
puts "\tq - Quit search."
|
304
|
+
puts "\t? - Print help."
|
305
|
+
else
|
306
|
+
puts "WARNING: invalid input. Type '?' for help."
|
307
|
+
end
|
308
|
+
|
309
|
+
next if search_actions.count > 0
|
310
|
+
|
311
|
+
puts
|
312
|
+
search_actions << @cli.ask('[slieq?] >> '){ |q| q.character = true }.downcase
|
313
|
+
end
|
314
|
+
when 'x'
|
315
|
+
actions << 'q'
|
316
|
+
actions << 'w'
|
317
|
+
when 'w'
|
318
|
+
if has_database_changes
|
319
|
+
tmp = "#{options[:database_path]}~"
|
320
|
+
backup = "#{options[:database_path]}~backup"
|
321
|
+
|
322
|
+
database['meta']['updated_at'] = DateTime.now.to_s
|
323
|
+
|
324
|
+
if File.exist?(options[:database_path])
|
325
|
+
puts "Backup old file to '#{backup}'."
|
326
|
+
FileUtils.mv(options[:database_path], backup)
|
327
|
+
end
|
328
|
+
|
329
|
+
puts "Write temp file to '#{tmp}'."
|
330
|
+
db_c = database.clone
|
331
|
+
db_c['hosts'] = db_c['hosts'].map{ |name, host| [name, host.to_h] }.to_h
|
332
|
+
File.binwrite(tmp, db_c.to_msgpack)
|
333
|
+
|
334
|
+
puts "Finally move temp file to '#{options[:database_path]}'."
|
335
|
+
FileUtils.mv(tmp, options[:database_path])
|
336
|
+
|
337
|
+
has_database_changes = false
|
338
|
+
else
|
339
|
+
puts 'Nothing changed, nothing written.'
|
340
|
+
end
|
341
|
+
when 'q'
|
342
|
+
if has_database_changes
|
343
|
+
puts
|
344
|
+
puts 'You have unsaved database changes.'
|
345
|
+
|
346
|
+
save = @cli.ask('Would you like to save the database? [Yn] ', String){ |q| q.character = true }.strip.downcase
|
347
|
+
save = 'y' if save == ''
|
348
|
+
puts "Answer: '#{save}'"
|
349
|
+
|
350
|
+
if save == 'y'
|
351
|
+
actions << 'x'
|
352
|
+
else
|
353
|
+
really = @cli.ask('Really? [yN] ', String){ |q| q.character = true }.strip.downcase
|
354
|
+
really = 'y' if really == ''
|
355
|
+
puts "Answer: '#{really}'"
|
356
|
+
|
357
|
+
if really == 'y'
|
358
|
+
has_database_changes = false
|
359
|
+
actions << 'q'
|
360
|
+
end
|
361
|
+
end
|
362
|
+
else
|
363
|
+
puts 'Quit.'
|
364
|
+
break
|
365
|
+
end
|
366
|
+
when '?'
|
367
|
+
puts
|
368
|
+
puts 'Main-menu help:'
|
369
|
+
puts "\tn - New password."
|
370
|
+
puts "\tl - List hosts from database."
|
371
|
+
puts "\ts - Search submenu."
|
372
|
+
puts "\tx - Same as wq."
|
373
|
+
puts "\tw - Write database to file."
|
374
|
+
puts "\tq - Quit."
|
375
|
+
puts "\t? - Print help."
|
376
|
+
else
|
377
|
+
puts "WARNING: invalid input. Type '?' for help."
|
378
|
+
end
|
379
|
+
|
380
|
+
next if actions.count > 0
|
381
|
+
|
382
|
+
puts
|
383
|
+
actions << @cli.ask('[nlsxwq?] > '){ |q| q.character = true }.downcase
|
384
|
+
end
|