ruby-keychain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ The MIT License
2
+ Copyright (c) 2012 Frederick Cheung
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,90 @@
1
+ A set of ruby bindings for the OS X keychain, written using ffi
2
+
3
+ Introduction
4
+ ============
5
+
6
+ The keychain is OS X's secure credential storage mechanism. This library allows access to internet passwords (typically specified as a combination of host, protocol, account (optionally port)) and generic passwords (identified by a service and account).
7
+
8
+
9
+ Working with keychains
10
+ ==================
11
+
12
+ Most operations will act on either the default keychain, or the default keychain search list. You can obtain specific keychains with
13
+
14
+ Keychain.default #the default keychain, usually /Users/<username>/Library/Keychains/<username>.keychain
15
+ Keychain.open(path) #opens a keychain file
16
+ Keychain.create(path, password) #creates a new keychain at the specified path
17
+
18
+
19
+
20
+ Searching for Keychain Items
21
+ =========
22
+
23
+ The top level constant `Keychain` as well as individual keychain objects have two methods `internet_passwords` and `generic_passwords` that return scope like objects. You can do
24
+
25
+ Keychain.internet_passwords.where(:server => 'example.com').all
26
+
27
+ to return Keychain::Item objects for that server
28
+
29
+ Keychain.internet_passwords.where(:server => 'example.com').first
30
+
31
+ to return the first Keychain::Item for that server or
32
+
33
+ Keychain.internet_passwords.where(:server => 'example.com').limit(4).all
34
+
35
+ to return up to 4 Keychain::Item for that server.
36
+
37
+ `generic_passwords` behaves similarly but searches the keychain for genereric passwords
38
+
39
+ You can restrict the search to a specific keychain with
40
+
41
+ some_keychain.internet_passwords.where(:server => 'example.com').all
42
+
43
+ returns matching `Keychain::Item` from the specified keychain.
44
+
45
+ or to an arbitrary list of keychains with
46
+
47
+ Keychain.internet_passwords.in(keychain_1, keychain2).all
48
+
49
+
50
+ Finding a Keychain::Item won't prompt the user for a password if the keychain is unlocked. Calling the password accessor method of the item may prompt the user for their password depending on the keychain item access settings.
51
+
52
+ If you call `where` multiple times, each successive invocation merges its conditions with the previous set of conditions
53
+
54
+
55
+ Creating keychain items
56
+ =========================
57
+
58
+ In the default keychain:
59
+
60
+ Keychain.internet_passwords.create(:server => 'example.com', :protocol => Keychain::Protocols::HTTP, :password => 'secret', :account => 'bob')
61
+
62
+ or
63
+
64
+ Keychain.generic_passwords.create(:service => 'AWS', :password => 'secret', :account => 'bob')
65
+
66
+ In a specific keychain
67
+
68
+ some_keychain.internet_passwords.create(...)
69
+
70
+ by default keychain items are only readable by the application that created them, however when running a ruby script the application is ruby: by default other ruby scripts will be able to read the items (if the keychain is unlocked).
71
+
72
+ Using keychain items
73
+ =====================
74
+
75
+ The `Keychain::Item` class has accessors for all its attributes, for the full list of attributes see `Sec::ATTR_MAP`
76
+
77
+ All strings returned are utf-8 encoded. Be careful not to set attribute values to strings with the ASCII_8BIT encoding as this will cause them to be treated as raw data rather than string. The exception to this is password data which the keychain api defines as being arbitrary binary data. When storing an actual password it is customary to use utf-8. The password data will always be returned as raw binary data
78
+
79
+
80
+ Error Handling
81
+ ==============
82
+
83
+ Failed operations will result in `Keychain::Error` being raised. The original error code is available as the `code` attribute of the exception. When attempting to insert a duplicate item, `Keychain::DuplicateItemError` (a subclass of `Keychain::Error`) is raised instead
84
+
85
+
86
+ Compatibility
87
+ =============
88
+ Requires ruby 1.9 due to use of encoding related methods. Should work in MRI and jruby. Not compatible with rubinius due to rubinius' ffi implemenation
89
+ not supporting certain features
90
+
data/lib/keychain.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'ffi'
2
+ require 'corefoundation'
3
+ require 'keychain/sec'
4
+ require 'keychain/keychain'
5
+ require 'keychain/error'
6
+ require 'keychain/item'
7
+ require 'keychain/scope'
8
+ require 'keychain/protocols'
9
+ # top level constant for this library
10
+ module Keychain
11
+ class << self
12
+ # creates a new keychain file and adds it to the keychain search path ( SecKeychainCreate )
13
+ #
14
+ # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCreate
15
+ # @param [String] path The path to the keychain file to create
16
+ # If it is not absolute it is interpreted relative to ~/Library/Keychains
17
+ # @param [String] password The password to use for the keychain
18
+ # @return [Keychain::Keychain] a keychain object representing the newly created keychain
19
+
20
+ def create(path, password)
21
+ password = password.encode(Encoding::UTF_8)
22
+ path = path.encode(Encoding::UTF_8)
23
+
24
+ out_buffer = FFI::MemoryPointer.new(:pointer)
25
+ status = Sec.SecKeychainCreate(path, password.bytesize, FFI::MemoryPointer.from_string(password), 0,
26
+ nil, out_buffer)
27
+
28
+ Sec.check_osstatus(status)
29
+ Keychain.new(out_buffer.read_pointer).release_on_gc
30
+ end
31
+
32
+ # Gets the default keychain object ( SecKeychainCopyDefault )
33
+ #
34
+ # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCopyDefault
35
+ # @return [Keychain::Keychain] a keychain object
36
+ def default
37
+ out_buffer = FFI::MemoryPointer.new(:pointer)
38
+ status = Sec.SecKeychainCopyDefault(out_buffer);
39
+ Sec.check_osstatus(status)
40
+
41
+ Keychain.new(out_buffer.read_pointer).release_on_gc
42
+ end
43
+
44
+ # Opens the keychain file at the specified path and adds it to the keychain search path ( SecKeychainOpen )
45
+ #
46
+ # Will succeed even if the file doesn't exists (however most operations on the keychain will then fail)
47
+ #
48
+ # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainCopyDefault
49
+ # @param [String] path Path to the keychain file
50
+ # @return [Keychain::Keychain] a keychain object
51
+ def open(path)
52
+ out_buffer = FFI::MemoryPointer.new(:pointer)
53
+ status = Sec.SecKeychainOpen(path,out_buffer);
54
+ Sec.check_osstatus(status)
55
+ Keychain.new(out_buffer.read_pointer).release_on_gc
56
+ end
57
+
58
+ # Returns a scope for internet passwords contained in all keychains
59
+ #
60
+ # @return [Keychain::Scope] a new scope object
61
+ def internet_passwords
62
+ Scope.new(Sec::Classes::INTERNET)
63
+ end
64
+
65
+ # Returns a scope for generic passwords in all keychains
66
+ #
67
+ # @return [Keychain::Scope] a new scope object
68
+ def generic_passwords
69
+ Scope.new(Sec::Classes::GENERIC)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module Sec
3
+ attach_function 'SecCopyErrorMessageString', [:osstatus, :pointer], :pointer
4
+ end
5
+
6
+ # The base class of all keychain related errors
7
+ #
8
+ # The original error code is available as `code`
9
+ class Keychain::Error < StandardError
10
+ attr_accessor :code
11
+ def initialize(code)
12
+ self.code = code
13
+ description = Sec.SecCopyErrorMessageString(code, nil)
14
+ if description.null?
15
+ super("Sec Error #{code}")
16
+ else
17
+ description = CF::Base.typecast(description)
18
+ super("#{description.to_s} (#{code})")
19
+ end
20
+ end
21
+ end
22
+
23
+ # Raised when saving or updating an item
24
+ # fails because an existing item is already in the keychain
25
+ class Keychain::DuplicateItemError < Keychain::Error; end
26
+ # Raised when an operation that requires a password fails,
27
+ # for example unlocking a keychain
28
+ class Keychain::AuthFailedError < Keychain::Error; end
29
+ # Raised when an action that requires user interaction
30
+ # (such as decrypting as password) is cancelled by the user
31
+ class Keychain::UserCancelledError < Keychain::Error; end
32
+ # Raised when an action fails because the underlying keychain
33
+ # does not exist
34
+ class Keychain::NoSuchKeychainError < Keychain::Error; end
@@ -0,0 +1,188 @@
1
+
2
+ module Sec
3
+ attach_function 'SecKeychainItemDelete', [:pointer], :osstatus
4
+ attach_function 'SecItemAdd', [:pointer, :pointer], :osstatus
5
+ attach_function 'SecItemUpdate', [:pointer, :pointer], :osstatus
6
+ attach_function 'SecKeychainItemCopyKeychain', [:pointer, :pointer], :osstatus
7
+
8
+ end
9
+
10
+ # An individual item from the keychain. Individual accessors are generated for the items attributes
11
+ #
12
+ #
13
+ class Keychain::Item < Sec::Base
14
+ attr_accessor :attributes
15
+ register_type 'SecKeychainItem'
16
+
17
+ # returns a programmer friendly description of the item
18
+ # @return [String]
19
+ def inspect
20
+ "<SecKeychainItem 0x#{@ptr.address.to_s(16)} #{service ? "service: #{service}" : "server: #{server}"} account: #{account}>"
21
+ end
22
+
23
+ Sec::ATTR_MAP.values.each do |ruby_name|
24
+ unless method_defined?(ruby_name)
25
+ define_method ruby_name do
26
+ @attributes[ruby_name]
27
+ end
28
+ define_method ruby_name.to_s+'=' do |value|
29
+ @attributes[ruby_name] = value
30
+ end
31
+ end
32
+ end
33
+
34
+ # Creates a new keychain item either from an FFI::Pointer or a hash of attributes
35
+ #
36
+ # @param [FFI::Pointer, Hash] attrs_or_pointer Either an FFI::Pointer to an existing
37
+ # SecKeychainItemRef to wrap or hash of attributes to create a new, unsaved Keychain::Item from
38
+ # see {Keychain::Scope#create}
39
+ #
40
+ def self.new(attrs_or_pointer)
41
+ if attrs_or_pointer.is_a? Hash
42
+ super(0).tap do |result|
43
+ attrs_or_pointer.each {|k,v| result.send("#{k}=", v)}
44
+ end
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ # @private
51
+ def initialize(*args)
52
+ super
53
+ @attributes = {}
54
+ end
55
+
56
+ # Removes the item from the associated keychain
57
+ #
58
+ def delete
59
+ status = Sec.SecKeychainItemDelete(self)
60
+ Sec.check_osstatus(status)
61
+ self
62
+ end
63
+
64
+ # Set a new password for the item
65
+ # @note The new password is not saved into the keychain until you call {Keychain::Item#save!}
66
+ # @param [String] value The new value for the password
67
+ # @return [String] The set value
68
+ def password=(value)
69
+ @unsaved_password = value
70
+ end
71
+
72
+ # Returns the keychain the item is in
73
+ #
74
+ # @return [Keychain::Keychain]
75
+ def keychain
76
+ out = FFI::MemoryPointer.new :pointer
77
+ status = Sec.SecKeychainItemCopyKeychain(self,out)
78
+ Sec.check_osstatus(status)
79
+ CF::Base.new(out.read_pointer).release_on_gc
80
+ end
81
+
82
+ # Fetches the password data associated with the item. This may cause the user to be asked for access
83
+ # @return [String] The password data, an ASCII_8BIT encoded string
84
+ def password
85
+ return @unsaved_password if @unsaved_password
86
+ out_buffer = FFI::MemoryPointer.new(:pointer)
87
+ status = Sec.SecItemCopyMatching({Sec::Query::ITEM_LIST => CF::Array.immutable([self]),
88
+ Sec::Query::CLASS => klass,
89
+ Sec::Query::RETURN_DATA => true}.to_cf, out_buffer)
90
+ Sec.check_osstatus(status)
91
+ CF::Base.typecast(out_buffer.read_pointer).to_s
92
+ end
93
+
94
+ # Attempts to update the keychain with any changes made to the item
95
+ # or saves a previously unpersisted item
96
+ # @param [optional, Hash] options extra options when saving the item
97
+ # @option options [Keychain::Keychain] :keychain when saving an unsaved item, they keychain to save it in
98
+ # @return [Keychain::Item] returns the item
99
+ def save!(options={})
100
+ if persisted?
101
+ cf_dict = update
102
+ else
103
+ cf_dict = create(options)
104
+ end
105
+ @unsaved_password = nil
106
+ update_self_from_dictionary(cf_dict)
107
+ cf_dict.release
108
+ self
109
+ end
110
+
111
+ # @private
112
+ def self.from_dictionary_of_attributes(cf_dict)
113
+ new(0).tap {|item| item.send :update_self_from_dictionary, cf_dict}
114
+ end
115
+
116
+ # Whether the item has been persisted to the keychain
117
+ # @return [Boolean]
118
+ def persisted?
119
+ !@ptr.null?
120
+ end
121
+
122
+ private
123
+
124
+ def create(options)
125
+ result = FFI::MemoryPointer.new :pointer
126
+ query = build_create_query(options)
127
+ query.merge!(build_new_attributes)
128
+ status = Sec.SecItemAdd(query, result);
129
+ Sec.check_osstatus(status)
130
+ cf_dict = CF::Base.typecast(result.read_pointer)
131
+ end
132
+
133
+ def update
134
+ status = Sec.SecItemUpdate({Sec::Query::ITEM_LIST => [self], Sec::INVERSE_ATTR_MAP[:klass] => klass}.to_cf, build_new_attributes);
135
+ Sec.check_osstatus(status)
136
+
137
+ result = FFI::MemoryPointer.new :pointer
138
+ query = build_refresh_query
139
+ status = Sec.SecItemCopyMatching(query, result);
140
+ Sec.check_osstatus(status)
141
+ cf_dict = CF::Base.typecast(result.read_pointer)
142
+ end
143
+
144
+
145
+
146
+ def update_self_from_dictionary(cf_dict)
147
+ if !persisted?
148
+ self.ptr = cf_dict[Sec::Value::REF].to_ptr
149
+ self.retain.release_on_gc
150
+ end
151
+ @attributes = cf_dict.inject({}) do |memo, (k,v)|
152
+ if ruby_name = Sec::ATTR_MAP[k]
153
+ memo[ruby_name] = v.to_ruby
154
+ end
155
+ memo
156
+ end
157
+ end
158
+
159
+ def build_create_query options
160
+ query = CF::Dictionary.mutable
161
+ query[Sec::Value::DATA] = CF::Data.from_string(@unsaved_password) if @unsaved_password
162
+ query[Sec::Query::KEYCHAIN] = options[:keychain] if options[:keychain]
163
+ query[Sec::Query::RETURN_ATTRIBUTES] = CF::Boolean::TRUE
164
+ query[Sec::Query::RETURN_REF] = CF::Boolean::TRUE
165
+ query
166
+ end
167
+
168
+ def build_refresh_query
169
+ query = CF::Dictionary.mutable
170
+ query[Sec::Query::ITEM_LIST] = CF::Array.immutable([self])
171
+ query[Sec::Query::RETURN_ATTRIBUTES] = CF::Boolean::TRUE
172
+ query[Sec::Query::RETURN_REF] = CF::Boolean::TRUE
173
+ query[Sec::INVERSE_ATTR_MAP[:klass]] = klass.to_cf
174
+ query
175
+ end
176
+
177
+ def build_new_attributes
178
+ new_attributes = CF::Dictionary.mutable
179
+ @attributes.each do |k,v|
180
+ next if k == :created_at || k == :updated_at
181
+ next if k == :klass && persisted?
182
+ k = Sec::INVERSE_ATTR_MAP[k]
183
+ new_attributes[k] = v.to_cf
184
+ end
185
+ new_attributes[Sec::Value::DATA] = CF::Data.from_string(@unsaved_password) if @unsaved_password
186
+ new_attributes
187
+ end
188
+ end
@@ -0,0 +1,188 @@
1
+
2
+ module Sec
3
+ attach_function 'SecKeychainCopyDefault', [:pointer], :osstatus
4
+ attach_function 'SecKeychainDelete', [:keychainref], :osstatus
5
+ attach_function 'SecKeychainOpen', [:string, :pointer], :osstatus
6
+ attach_function 'SecKeychainGetPath', [:keychainref, :pointer, :pointer], :osstatus
7
+
8
+ attach_function 'SecKeychainCreate', [:string, :uint32, :pointer, :char, :pointer, :pointer], :osstatus
9
+ attach_function 'SecItemCopyMatching', [:pointer, :pointer], :osstatus
10
+
11
+ #@private
12
+ class KeychainSettings < FFI::Struct
13
+ layout :version, :uint32,
14
+ :lock_on_sleep, :uchar,
15
+ :use_lock_interval, :uchar, #apple ignores this
16
+ :lock_interval, :uint32
17
+ end
18
+
19
+ attach_function 'SecKeychainSetSettings', [:keychainref, KeychainSettings], :osstatus
20
+ attach_function 'SecKeychainCopySettings', [:keychainref, KeychainSettings], :osstatus
21
+
22
+ attach_function 'SecKeychainLock', [:keychainref], :osstatus
23
+ attach_function 'SecKeychainUnlock', [:keychainref, :uint32, :pointer, :uchar], :osstatus
24
+
25
+ attach_function 'SecKeychainGetStatus', [:keychainref, :pointer], :osstatus
26
+
27
+ enum :keychainStatus, [
28
+ :kSecUnlockStateStatus, 1,
29
+ :kSecReadPermStatus, 2,
30
+ :kSecWritePermStatus, 4,
31
+ ]
32
+ end
33
+
34
+ module Keychain
35
+ # Wrapper class for individual keychains. Corresponds to a SecKeychainRef
36
+ #
37
+ class Keychain < Sec::Base
38
+ register_type 'SecKeychain'
39
+
40
+ # Returns whether the keychain will be locked if the machine goes to sleep
41
+ #
42
+ # @return [Boolean]
43
+ #
44
+ def lock_on_sleep?
45
+ get_settings[:lock_on_sleep] != 0
46
+ end
47
+
48
+ # Returns the duration (in seconds) after which the keychain will be locked
49
+ #
50
+ # @return [Boolean]
51
+ #
52
+ def lock_interval
53
+ get_settings[:lock_interval]
54
+ end
55
+
56
+ # Set whether the keychain will be locked if the machine goes to sleep
57
+ #
58
+ # @param [Boolean] value
59
+ #
60
+ def lock_on_sleep= value
61
+ put_settings(get_settings.tap {|s| s[:lock_on_sleep] = value ? 1 : 0})
62
+ end
63
+
64
+ # Sets the duration (in seconds) after which the keychain will be locked
65
+ #
66
+ # @param [Integer] value dutarion in seconds
67
+ #
68
+ def lock_interval= value
69
+ put_settings(get_settings.tap {|s| s[:lock_interval] = value})
70
+ end
71
+
72
+ # Returns a scope for internet passwords contained in this keychain
73
+ #
74
+ # @return [Keychain::Scope] a new scope object
75
+ def internet_passwords
76
+ Scope.new(Sec::Classes::INTERNET, self)
77
+ end
78
+
79
+ # Returns a scope for generic passwords contained in this keychain
80
+ #
81
+ # @return [Keychain::Scope] a new scope object
82
+ def generic_passwords
83
+ Scope.new(Sec::Classes::GENERIC, self)
84
+ end
85
+
86
+ # returns a description of the keychain
87
+ # @return [String]
88
+ def inspect
89
+ "<SecKeychain 0x#{@ptr.address.to_s(16)}: #{path}>"
90
+ end
91
+
92
+ # Removes the keychain from the search path and deletes the corresponding file (SecKeychainDelete)
93
+ #
94
+ # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainDelete
95
+ # @return self
96
+ def delete
97
+ status = Sec.SecKeychainDelete(self)
98
+ Sec.check_osstatus(status)
99
+ self
100
+ end
101
+
102
+ # Returns the path at which the keychain is stored
103
+ #
104
+ # See https://developer.apple.com/library/mac/documentation/security/Reference/keychainservices/Reference/reference.html#//apple_ref/c/func/SecKeychainGetPath
105
+ #
106
+ # @return [String] path to the keychain file
107
+ def path
108
+ out_buffer = FFI::MemoryPointer.new(:uchar, 2048)
109
+ io_size = FFI::MemoryPointer.new(:uint32)
110
+ io_size.put_uint32(0, out_buffer.size)
111
+
112
+ status = Sec.SecKeychainGetPath(self,io_size, out_buffer)
113
+ Sec.check_osstatus(status)
114
+
115
+ out_buffer.read_string(io_size.get_uint32(0)).force_encoding(Encoding::UTF_8)
116
+ end
117
+
118
+ # Locks the keychain
119
+ #
120
+ def lock!
121
+ status = Sec.SecKeychainLock(self)
122
+ Sec.check_osstatus status
123
+ end
124
+
125
+ # Unlocks the keychain
126
+ #
127
+ # @param [optional, String] password the password to unlock the keychain with. If no password is supplied the keychain will prompt the user for a password
128
+ def unlock! password=nil
129
+ if password
130
+ password = password.encode(Encoding::UTF_8)
131
+ status = Sec.SecKeychainUnlock self, password.bytesize, password, 1
132
+ else
133
+ status = Sec.SecKeychainUnlock self, 0, nil, 0
134
+ end
135
+ Sec.check_osstatus status
136
+ end
137
+
138
+ # Returns whether the keychain is locked
139
+ # @return [Boolean]
140
+ def locked?
141
+ !status_flag?(:kSecUnlockStateStatus)
142
+ end
143
+
144
+ # Returns whether the keychain is readable
145
+ # @return [Boolean]
146
+ def readable?
147
+ status_flag?(:kSecReadPermStatus)
148
+ end
149
+
150
+ # Returns whether the keychain is writable
151
+ # @return [Boolean]
152
+ def writeable?
153
+ status_flag?(:kSecWritePermStatus)
154
+ end
155
+
156
+ def exists?
157
+ begin
158
+ readable?
159
+ true
160
+ rescue NoSuchKeychainError
161
+ false
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def status_flag? enum_name
168
+ out = FFI::MemoryPointer.new(:uint32)
169
+ status = Sec.SecKeychainGetStatus(self,out);
170
+ Sec.check_osstatus status
171
+ (out.get_uint32(0) & Sec.enum_value(enum_name)).nonzero?
172
+ end
173
+
174
+ def get_settings
175
+ settings = Sec::KeychainSettings.new
176
+ settings[:version] = 1
177
+ status = Sec.SecKeychainCopySettings(self, settings)
178
+ Sec.check_osstatus status
179
+ settings
180
+ end
181
+
182
+ def put_settings settings
183
+ status = Sec.SecKeychainSetSettings(self, settings)
184
+ Sec.check_osstatus status
185
+ settings
186
+ end
187
+ end
188
+ end