osp 0.1.0.pre.dev.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|