keyring 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +62 -70
- data/Rakefile +10 -15
- data/bin/keyring +12 -0
- data/keyring.gemspec +28 -0
- data/lib/keyring.rb +22 -13
- data/lib/keyring/backend.rb +42 -0
- data/lib/keyring/backends/macosx_keychain.rb +91 -0
- data/lib/keyring/backends/memory.rb +29 -0
- data/lib/keyring/cli.rb +79 -0
- data/lib/keyring/version.rb +6 -0
- data/test/keyring_tests.rb +7 -0
- data/test/test_backend.rb +38 -0
- data/test/test_backend_macosx_keychain.rb +73 -0
- data/test/test_backend_memory.rb +43 -0
- data/test/test_cli.rb +58 -0
- data/test/test_keyring.rb +36 -0
- data/test/testcmds/macosx/binary +1 -0
- data/test/testcmds/macosx/emptything +1 -0
- data/test/testcmds/macosx/random +1 -0
- data/test/testcmds/macosx/security-delete +27 -0
- data/test/testcmds/macosx/security-find +26 -0
- data/test/testcmds/macosx/security-findempty +26 -0
- data/test/testcmds/macosx/security-findhex +26 -0
- data/test/testcmds/macosx/security-notfound +6 -0
- data/test/testcmds/macosx/security-righthelp +51 -0
- data/test/testcmds/macosx/security-wronghelp +7 -0
- metadata +129 -59
- data/History.txt +0 -4
- data/lib/keyring/Keyring.rb +0 -95
- data/lib/keyring/SimpleKeyring.rb +0 -38
- data/spec/keyring_spec.rb +0 -129
- data/spec/spec_helper.rb +0 -15
- data/version.txt +0 -1
data/lib/keyring/cli.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require 'slop'
|
5
|
+
require 'keyring'
|
6
|
+
|
7
|
+
class Keyring::CLI
|
8
|
+
def initialize(options={})
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
@method = @arg = nil
|
12
|
+
@opts = Slop.parse(:help => true) do
|
13
|
+
banner 'Usage: keyring get|set|delete <service> <username> [password (for set)]'
|
14
|
+
|
15
|
+
on :v, :version, 'Print the version' do
|
16
|
+
puts Keyring::VERSION
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
command 'set' do
|
21
|
+
# FIXME: option to prompt for password
|
22
|
+
run do |opts, args|
|
23
|
+
@method = :set
|
24
|
+
@arg = args
|
25
|
+
end
|
26
|
+
end
|
27
|
+
command 'get' do
|
28
|
+
run do |opts, args|
|
29
|
+
@method = :get
|
30
|
+
@arg = args
|
31
|
+
end
|
32
|
+
end
|
33
|
+
command 'delete' do
|
34
|
+
run do |opts, args|
|
35
|
+
@method = :delete
|
36
|
+
@arg = args
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def main
|
43
|
+
if @method && respond_to?(@method)
|
44
|
+
send(@method, @arg)
|
45
|
+
else
|
46
|
+
puts @opts
|
47
|
+
exit 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def set(args)
|
52
|
+
ensure_arg_presence(args, 3)
|
53
|
+
keyring = Keyring.new
|
54
|
+
keyring.set_password(args[0], args[1], args[2])
|
55
|
+
end
|
56
|
+
def get(args)
|
57
|
+
ensure_arg_presence(args, 2)
|
58
|
+
keyring = Keyring.new
|
59
|
+
puts keyring.get_password(args[0], args[1])
|
60
|
+
end
|
61
|
+
def delete(args)
|
62
|
+
ensure_arg_presence(args, 2)
|
63
|
+
keyring = Keyring.new
|
64
|
+
keyring.delete_password(args[0], args[1])
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_arg_presence(args, count)
|
68
|
+
0.upto(count-1) do |i|
|
69
|
+
if !args[i] || args[i].empty?
|
70
|
+
if !@options[:testing]
|
71
|
+
puts @opts
|
72
|
+
exit 1
|
73
|
+
else
|
74
|
+
raise ArgumentError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
module KeyringTestConstants
|
5
|
+
# Fake commands used in place of system commands
|
6
|
+
TESTCMDDIR = File.expand_path('testcmds', File.dirname(__FILE__))
|
7
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'keyring'
|
6
|
+
|
7
|
+
class KeyringBackendTests < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@backend = Keyring::Backend.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Keyring::Backend::Test < Keyring::Backend; end
|
13
|
+
def test_register_implementation
|
14
|
+
Keyring::Backend.register_implementation(Keyring::Backend::Test)
|
15
|
+
assert Keyring::Backend.implementations.include?(Keyring::Backend::Test)
|
16
|
+
Keyring::Backend.implementations.delete(Keyring::Backend::Test)
|
17
|
+
end
|
18
|
+
def test_create
|
19
|
+
# This should be a bit more thorough
|
20
|
+
assert_kind_of Keyring::Backend, Keyring::Backend.create
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_supported
|
24
|
+
refute @backend.supported?
|
25
|
+
end
|
26
|
+
def test_priority
|
27
|
+
assert_equal 0, @backend.priority
|
28
|
+
end
|
29
|
+
def test_set_password
|
30
|
+
assert_raises(NotImplementedError) {@backend.set_password('service', 'username', 'password')}
|
31
|
+
end
|
32
|
+
def test_get_password
|
33
|
+
assert_raises(NotImplementedError) {@backend.get_password('service', 'username')}
|
34
|
+
end
|
35
|
+
def test_delete_password
|
36
|
+
assert_raises(NotImplementedError) {@backend.delete_password('service', 'username')}
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require File.expand_path('keyring_tests', File.dirname(__FILE__))
|
5
|
+
require 'test/unit'
|
6
|
+
require 'mocha/setup'
|
7
|
+
require 'keyring'
|
8
|
+
|
9
|
+
class KeyringBackendMacosxKeychainTests < Test::Unit::TestCase
|
10
|
+
include KeyringTestConstants
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@backend = Keyring::Backend::MacosxKeychain.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_supported
|
17
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-wronghelp')
|
18
|
+
refute @backend.supported?
|
19
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/bogus')
|
20
|
+
refute @backend.supported?
|
21
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-righthelp')
|
22
|
+
assert @backend.supported?
|
23
|
+
end
|
24
|
+
def test_priority
|
25
|
+
assert_equal 1, @backend.priority
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_security_command
|
29
|
+
assert_equal 'add-generic-password', @backend.security_command('add')
|
30
|
+
assert_equal 'find-generic-password', @backend.security_command('find')
|
31
|
+
assert_equal 'delete-generic-password', @backend.security_command('delete')
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_set_password
|
35
|
+
@backend.expects(:system).with(
|
36
|
+
'/usr/bin/security', 'add-generic-password', '-s', 'service', '-a', 'username', '-w', 'password', '-U')
|
37
|
+
# We need to set $? to make up for the fact that system was not actually run
|
38
|
+
# FIXME: surely there's a better way?
|
39
|
+
`true`
|
40
|
+
@backend.set_password('service', 'username', 'password')
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_get_password
|
44
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-find')
|
45
|
+
assert_equal 'password', @backend.get_password('service', 'username')
|
46
|
+
end
|
47
|
+
def test_get_password_hex
|
48
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-findhex')
|
49
|
+
assert_equal "\0100\0101\0100\0101\0100\0101\0100\0101", @backend.get_password('service', 'hexuser')
|
50
|
+
end
|
51
|
+
def test_get_password_empty
|
52
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-findempty')
|
53
|
+
assert_equal '', @backend.get_password('service', 'emptyuser')
|
54
|
+
end
|
55
|
+
def test_get_password_notfound
|
56
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-notfound')
|
57
|
+
assert_nil @backend.get_password('bogus', 'bogus')
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_delete_password_options
|
61
|
+
Open3.expects(:popen3).with(
|
62
|
+
'/usr/bin/security', 'delete-generic-password', '-s', 'service', '-a', 'username')
|
63
|
+
@backend.delete_password('service', 'username')
|
64
|
+
end
|
65
|
+
def test_delete_password_output
|
66
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-delete')
|
67
|
+
assert @backend.delete_password('service', 'gooduser')
|
68
|
+
end
|
69
|
+
def test_delete_password_errors
|
70
|
+
@backend.security = File.join(TESTCMDDIR, 'macosx/security-notfound')
|
71
|
+
refute @backend.delete_password('bogus', 'bogus')
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'keyring'
|
6
|
+
|
7
|
+
class KeyringBackendMemoryTests < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@backend = Keyring::Backend::Memory.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_supported
|
13
|
+
assert @backend.supported?
|
14
|
+
end
|
15
|
+
def test_priority
|
16
|
+
assert_equal 0.1, @backend.priority
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_set_password
|
20
|
+
@backend.set_password('service', 'username', 'password')
|
21
|
+
assert_equal 'password', @backend.instance_variable_get('@keyring')['service']['username']
|
22
|
+
end
|
23
|
+
def test_get_password
|
24
|
+
@backend.set_password('service', 'username', 'password')
|
25
|
+
assert_equal 'password', @backend.get_password('service', 'username')
|
26
|
+
|
27
|
+
# Ensure that get_password behaves properly when asked for things that
|
28
|
+
# have not been set
|
29
|
+
assert_nil @backend.get_password('service', 'bogus')
|
30
|
+
assert_nil @backend.get_password('bogus', 'bogus')
|
31
|
+
end
|
32
|
+
def test_delete_password
|
33
|
+
@backend.set_password('service', 'username', 'password')
|
34
|
+
@backend.delete_password('service', 'username')
|
35
|
+
assert_nil @backend.instance_variable_get('@keyring')['service']['username']
|
36
|
+
assert_nil @backend.get_password('service', 'username')
|
37
|
+
|
38
|
+
# Ensure that delete_password behaves properly when asked to delete
|
39
|
+
# things that have not been set
|
40
|
+
@backend.delete_password('service', 'bogus')
|
41
|
+
@backend.delete_password('bogus', 'bogus')
|
42
|
+
end
|
43
|
+
end
|
data/test/test_cli.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require 'keyring/cli'
|
5
|
+
require 'test/unit'
|
6
|
+
require 'mocha/setup'
|
7
|
+
|
8
|
+
class KeyringCLITests < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@cli = Keyring::CLI.new(testing: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
EXE = File.expand_path('../bin/keyring', File.dirname(__FILE__))
|
14
|
+
def test_help_option
|
15
|
+
assert_match /\AUsage:/, `#{EXE} --help`
|
16
|
+
assert $?.success?
|
17
|
+
end
|
18
|
+
def test_version_option
|
19
|
+
assert_equal "#{Keyring::VERSION}\n", `#{EXE} --version`
|
20
|
+
assert $?.success?
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_set
|
24
|
+
assert_raise(ArgumentError) {@cli.set([])}
|
25
|
+
assert_raise(ArgumentError) {@cli.set(['service'])}
|
26
|
+
assert_raise(ArgumentError) {@cli.set(['service', 'username'])}
|
27
|
+
|
28
|
+
Keyring.any_instance.expects(:set_password).with('service', 'username', 'password')
|
29
|
+
@cli.set(['service', 'username', 'password'])
|
30
|
+
end
|
31
|
+
def test_get
|
32
|
+
assert_raise(ArgumentError) {@cli.get([])}
|
33
|
+
assert_raise(ArgumentError) {@cli.get(['service'])}
|
34
|
+
|
35
|
+
Keyring.any_instance.expects(:get_password).with('service', 'username').returns('password')
|
36
|
+
$stdout = output = StringIO.new
|
37
|
+
@cli.get(['service', 'username'])
|
38
|
+
$stdout = STDOUT
|
39
|
+
assert_equal "password\n", output.string
|
40
|
+
end
|
41
|
+
def test_delete
|
42
|
+
assert_raise(ArgumentError) {@cli.delete([])}
|
43
|
+
assert_raise(ArgumentError) {@cli.delete(['service'])}
|
44
|
+
|
45
|
+
Keyring.any_instance.expects(:delete_password).with('service', 'username')
|
46
|
+
@cli.delete(['service', 'username'])
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_ensure_arg_presence
|
50
|
+
assert_nothing_raised {@cli.ensure_arg_presence([], 0)}
|
51
|
+
assert_raise(ArgumentError) {@cli.ensure_arg_presence([], 1)}
|
52
|
+
assert_raise(ArgumentError) {@cli.ensure_arg_presence([''], 1)}
|
53
|
+
|
54
|
+
assert_nothing_raised {@cli.ensure_arg_presence(['a'], 1)}
|
55
|
+
assert_raise(ArgumentError) {@cli.ensure_arg_presence(['a'], 2)}
|
56
|
+
assert_raise(ArgumentError) {@cli.ensure_arg_presence(['a', ''], 2)}
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# keyring: System keyring abstraction library
|
2
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'mocha/setup'
|
6
|
+
require 'keyring'
|
7
|
+
|
8
|
+
class KeyringTests < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@backend = Keyring::Backend::Memory.new
|
11
|
+
@keyring = Keyring.new(@backend)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_initialize
|
15
|
+
keyring = Keyring.new
|
16
|
+
assert_kind_of Keyring::Backend, keyring.instance_variable_get(:@backend)
|
17
|
+
|
18
|
+
backend = Keyring::Backend::Memory.new
|
19
|
+
keyring = Keyring.new(backend)
|
20
|
+
assert_equal backend, keyring.instance_variable_get(:@backend)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_get_password
|
24
|
+
@backend.expects(:get_password).with('service', 'username').returns('password')
|
25
|
+
password = @keyring.get_password('service', 'username')
|
26
|
+
assert_equal 'password', password
|
27
|
+
end
|
28
|
+
def test_set_password
|
29
|
+
@backend.expects(:set_password).with('service', 'username', 'password')
|
30
|
+
@keyring.set_password('service', 'username', 'password')
|
31
|
+
end
|
32
|
+
def test_delete_password
|
33
|
+
@backend.expects(:delete_password).with('service', 'username')
|
34
|
+
@keyring.delete_password('service', 'username')
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
01010101
|
@@ -0,0 +1 @@
|
|
1
|
+
password:
|
@@ -0,0 +1 @@
|
|
1
|
+
ô%A���t;. u�@r
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# keyring: System keyring abstraction library
|
3
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
|
+
|
5
|
+
cat <<-EOF
|
6
|
+
keychain: "/Users/jheiss/Library/Keychains/login.keychain"
|
7
|
+
class: "genp"
|
8
|
+
attributes:
|
9
|
+
0x00000007 <blob>="service"
|
10
|
+
0x00000008 <blob>=<NULL>
|
11
|
+
"acct"<blob>="username"
|
12
|
+
"cdat"<timedate>=0x32303133313132303233353532395A00 "20131120235529Z\000"
|
13
|
+
"crtr"<uint32>=<NULL>
|
14
|
+
"cusi"<sint32>=<NULL>
|
15
|
+
"desc"<blob>=<NULL>
|
16
|
+
"gena"<blob>=<NULL>
|
17
|
+
"icmt"<blob>=<NULL>
|
18
|
+
"invi"<sint32>=<NULL>
|
19
|
+
"mdat"<timedate>=0x32303133313132313232353534395A00 "20131121225549Z\000"
|
20
|
+
"nega"<sint32>=<NULL>
|
21
|
+
"prot"<blob>=<NULL>
|
22
|
+
"scrp"<sint32>=<NULL>
|
23
|
+
"svce"<blob>="service"
|
24
|
+
"type"<uint32>=<NULL>
|
25
|
+
EOF
|
26
|
+
|
27
|
+
printf "password has been deleted.\n" >&2
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# keyring: System keyring abstraction library
|
3
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
|
+
|
5
|
+
cat <<-EOF
|
6
|
+
keychain: "/Users/example/Library/Keychains/login.keychain"
|
7
|
+
class: "genp"
|
8
|
+
attributes:
|
9
|
+
0x00000007 <blob>="service"
|
10
|
+
0x00000008 <blob>=<NULL>
|
11
|
+
"acct"<blob>="username"
|
12
|
+
"cdat"<timedate>=0x32303133313132303233353532395A00 "20131120235529Z\000"
|
13
|
+
"crtr"<uint32>=<NULL>
|
14
|
+
"cusi"<sint32>=<NULL>
|
15
|
+
"desc"<blob>=<NULL>
|
16
|
+
"gena"<blob>=<NULL>
|
17
|
+
"icmt"<blob>=<NULL>
|
18
|
+
"invi"<sint32>=<NULL>
|
19
|
+
"mdat"<timedate>=0x32303133313132313132343834315A00 "20131121124841Z\000"
|
20
|
+
"nega"<sint32>=<NULL>
|
21
|
+
"prot"<blob>=<NULL>
|
22
|
+
"scrp"<sint32>=<NULL>
|
23
|
+
"svce"<blob>="service"
|
24
|
+
"type"<uint32>=<NULL>
|
25
|
+
EOF
|
26
|
+
printf "password: \"password\"\n" >&2
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# keyring: System keyring abstraction library
|
3
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
|
+
|
5
|
+
cat <<-EOF
|
6
|
+
keychain: "/Users/example/Library/Keychains/login.keychain"
|
7
|
+
class: "genp"
|
8
|
+
attributes:
|
9
|
+
0x00000007 <blob>="service"
|
10
|
+
0x00000008 <blob>=<NULL>
|
11
|
+
"acct"<blob>="emptyuser"
|
12
|
+
"cdat"<timedate>=0x32303133313132303233353532395A00 "20131120235529Z\000"
|
13
|
+
"crtr"<uint32>=<NULL>
|
14
|
+
"cusi"<sint32>=<NULL>
|
15
|
+
"desc"<blob>=<NULL>
|
16
|
+
"gena"<blob>=<NULL>
|
17
|
+
"icmt"<blob>=<NULL>
|
18
|
+
"invi"<sint32>=<NULL>
|
19
|
+
"mdat"<timedate>=0x32303133313132313132343834315A00 "20131121124841Z\000"
|
20
|
+
"nega"<sint32>=<NULL>
|
21
|
+
"prot"<blob>=<NULL>
|
22
|
+
"scrp"<sint32>=<NULL>
|
23
|
+
"svce"<blob>="service"
|
24
|
+
"type"<uint32>=<NULL>
|
25
|
+
EOF
|
26
|
+
printf "password: \n" >&2
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# keyring: System keyring abstraction library
|
3
|
+
# License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
4
|
+
|
5
|
+
cat <<-EOF
|
6
|
+
keychain: "/Users/example/Library/Keychains/login.keychain"
|
7
|
+
class: "genp"
|
8
|
+
attributes:
|
9
|
+
0x00000007 <blob>="service"
|
10
|
+
0x00000008 <blob>=<NULL>
|
11
|
+
"acct"<blob>="hexuser"
|
12
|
+
"cdat"<timedate>=0x32303133313132303233353532395A00 "20131120235529Z\000"
|
13
|
+
"crtr"<uint32>=<NULL>
|
14
|
+
"cusi"<sint32>=<NULL>
|
15
|
+
"desc"<blob>=<NULL>
|
16
|
+
"gena"<blob>=<NULL>
|
17
|
+
"icmt"<blob>=<NULL>
|
18
|
+
"invi"<sint32>=<NULL>
|
19
|
+
"mdat"<timedate>=0x32303133313132313132343834315A00 "20131121124841Z\000"
|
20
|
+
"nega"<sint32>=<NULL>
|
21
|
+
"prot"<blob>=<NULL>
|
22
|
+
"scrp"<sint32>=<NULL>
|
23
|
+
"svce"<blob>="service"
|
24
|
+
"type"<uint32>=<NULL>
|
25
|
+
EOF
|
26
|
+
printf "password: 0x08300831083008310830083108300831 \"\0100\0101\0100\0101\0100\0101\0100\0101\"\n" >&2
|