reedb 0.10.rc1

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.
@@ -0,0 +1,268 @@
1
+ # ====================================================
2
+ # Copyright 2015 Lonely Robot (see @author)
3
+ # @author: Katharina Sabel | www.2rsoftworks.de
4
+ #
5
+ # Distributed under the GNU Lesser GPL Version 3
6
+ # (See accompanying LICENSE file or get a copy at
7
+ # https://www.gnu.org/licenses/lgpl.html)
8
+ # ====================================================
9
+
10
+ require_relative 'utils/version'
11
+ require_relative 'security/encryption'
12
+
13
+ module Reedb
14
+ class DataFile
15
+
16
+ # Name of the datafile in clear.
17
+ # To get the hashed variant use the path getter.
18
+ #
19
+ attr_reader :name
20
+
21
+ # Path and hash of the file on the system. Used when caching files in a vault
22
+ #
23
+ attr_reader :path
24
+
25
+ # The latest version of this data file. Contains a version numeral and
26
+ # a last-changed timestamp to provide better history for
27
+ # multi-computer setups.
28
+ #
29
+ attr_reader :version
30
+
31
+ # attr_reader :dataset
32
+
33
+ # Default constructor for a datafile.
34
+ #
35
+ def initialize(name, vault, old_file = nil)
36
+ if old_file
37
+ @dataset = old_file
38
+ @name = @dataset['header']['name']
39
+ @version = Version1.new(@dataset['header']['latest'])
40
+ @vault = vault
41
+ else
42
+ @version = Version1.new
43
+ @vault = vault
44
+ @name = name
45
+ fill_file
46
+ end
47
+
48
+ # The header set is defined by the vault and specifies the fields that can
49
+ # be specified in the header part of a file.
50
+ construct_path(@name, @vault)
51
+ end
52
+
53
+ # New and improved data insetion function!
54
+ # Data needs to have a certain format
55
+ # {
56
+ # 'header': {
57
+ # ...
58
+ # },
59
+ # 'body' {
60
+ # ...
61
+ # }
62
+ # }
63
+ #
64
+ # Malformed data will be ignored and reported in the vault logging.
65
+ #
66
+ # Writing into body is farely straight forward. A value needs a field and is
67
+ # assigned a version automatically depending on the time in the cache cycle
68
+ # of the file
69
+ #
70
+ # Writing to header is much more interesting.
71
+ # There is a header_set defined in the parent vault that specifies what fields
72
+ # are valid and what types they allow. For example, by default, there is a 'urls'
73
+ # field that is a list of urls.
74
+ #
75
+ # WRITING THE SAME VALUE INTO A FIELD AS EXISTS BEFORE WILL RESET THAT VALUE!
76
+ # That's how you can delete a field with this same function in the same step as
77
+ # adding new information (I know...hacky. But clever)
78
+ #
79
+ # For further documentation on this function, please check with the Reedb wiki
80
+ #
81
+ # Params:
82
+ # data => specifically formatted JSON data set (see docs)
83
+ # mode => overwrite default file/ vault caching mode.
84
+ #
85
+ def insertv2(data, mode = :hard)
86
+ # Add option for :fast sync mode here at some point
87
+ sync if mode == :hard
88
+
89
+ # First split up the data and check what's actually there
90
+ to_head = if data['header'] then data['header'] else false end
91
+ to_body = if data['body'] then data['body'] else false end
92
+
93
+ # Returns an error code for malformed data
94
+ return FILE_MALFORMED_DATA_ERROR unless to_body || to_head
95
+
96
+ # Gets the time difference between edits.
97
+ # If it's not big enough (5 seconds) will not increment version number
98
+ current_time = DateTime.now.strftime('%Q').to_i
99
+ old_time = @version.timestamp.to_i
100
+ version_needs_changing = (current_time - old_time >= FILE_CACHE_TIME) && to_body
101
+
102
+ # Actually updates the version if neccessary
103
+ @version.update if version_needs_changing
104
+
105
+ # Now make sure the version branch actually exists
106
+ @dataset['body']["#{@version}"] = {} unless @dataset['body']["#{@version}"]
107
+
108
+ # Update the latest version pointer in the dataset
109
+ @dataset['header']['latest'] = "#{@version}" if version_needs_changing
110
+
111
+ if to_head
112
+ to_head.each do |key, value|
113
+ @dataset['header']['name'] if key == 'name'
114
+
115
+ # This means that the value can be stored
116
+ if @vault.header_set.include?(key)
117
+
118
+ # If inserting into a header single field
119
+ if @vault.header_set[key] == 'single'
120
+ if @dataset['header'][key]
121
+ @dataset['header'][key] = nil
122
+ else
123
+ @dataset['header'][key] = value
124
+ end
125
+
126
+ # If inserting into a header list
127
+ elsif @vault.header_set[key] == 'list'
128
+ if value.instance_of? String
129
+ if @dataset['header'][key].include?(value)
130
+ @dataset['header'][key].delete(value)
131
+ else
132
+ @dataset['header'][key] << value
133
+ end
134
+ elsif value.instance_of? Array
135
+ value.each do |sub_value|
136
+ if @dataset['header'][key].include?(sub_value)
137
+ @dataset['header'][key].delete(sub_value)
138
+ else
139
+ @dataset['header'][key] << sub_value
140
+ end
141
+ end
142
+ end
143
+
144
+ # If inserting into a header tree
145
+ elsif @vault.header_set[key] == 'tree'
146
+ if @dataset['header'][key].include?(value)
147
+ @dataset['header'][key].delete(value)
148
+ else
149
+ @dataset['header'][key][value[0]] = value[1]
150
+ end
151
+ end
152
+ else
153
+ # Report the incident
154
+ VaultLogger.write("Tried to write invalid data to #{@name}", 'warn')
155
+ end
156
+ end
157
+ end
158
+
159
+ # BOOORING! :C
160
+ if to_body
161
+ to_body.each do |field, value|
162
+ @dataset['body']["#{@version}"]["#{field}"] = value
163
+ end
164
+ end
165
+
166
+ return self
167
+ end
168
+
169
+ #
170
+ #
171
+ #
172
+
173
+ def insert(data, mode = :hard)
174
+ puts "[LEGACY MODE]: This function has been depreciated. Use 'insertv2' instead!"
175
+ VaultLogger.write("[LEGACY MODE]: Using broken function. Aborting action!", 'error')
176
+ return
177
+
178
+ # => Updates the version of the file if neccesary
179
+ # puts (@dataset['body'][@version] == {})
180
+
181
+ # puts @dataset['body'][@version]
182
+ curr_time = DateTime.now.strftime('%Q').to_i
183
+ then_time = @version.timestamp
184
+ # time_diff = @version.timestamp - curr_time
185
+ time_dif = curr_time - then_time
186
+
187
+ unless @dataset['body'][@version] == {}
188
+ unless time_dif < 7500
189
+ @version.update
190
+ end
191
+ end
192
+
193
+ data.each do |master, section|
194
+ if master == 'body'
195
+ @dataset[master]["#{@version}"] = {} unless @dataset[master]["#{@version}"]
196
+ end
197
+
198
+ section.each do |key, value|
199
+ if master == 'header'
200
+ @dataset[master][key] = value
201
+ next
202
+ end
203
+
204
+ @dataset[master]["#{@version}"][key] = value
205
+ @dataset['header']['latest'] = @version
206
+ end
207
+ end
208
+ sync if mode == :hard
209
+ end
210
+
211
+ def cache mode
212
+ return @dataset['header'] if mode == :header
213
+ return @dataset if mode == :full
214
+ end
215
+
216
+ # Has two modes. In soft-mode it schedules a sync with the file system.
217
+ # In hard-mode (default) it force syncs itself with the filesystem by itself after every insert.
218
+ #
219
+ #
220
+ #
221
+ def sync(mode = :hard)
222
+ json_data = @dataset.to_json
223
+ # puts json_data
224
+ crypt_json = @vault.crypt.encrypt(json_data)
225
+
226
+ tmp = File.open("#{@path}", "wb+")
227
+ tmp.write(Base64.encode64("#{crypt_json}"))
228
+ tmp.close
229
+ VaultLogger.write("File #{@name} has been synced", 'debug')
230
+ return self
231
+ end
232
+
233
+ # Closes the file on the file system and dumps the header
234
+ # reference from the containing vault.
235
+ # Usually only called on vault close & lock.
236
+ #
237
+ def close
238
+ sync
239
+ remove_instance_variable(:@dataset)
240
+ VaultLogger.write("Closing file #{name}", 'debug')
241
+ end
242
+
243
+ private
244
+
245
+ def fill_file
246
+ @dataset = {}
247
+ @dataset['header'] = {}
248
+ @dataset['header']['name'] = @name
249
+ @dataset['header']['latest'] = @version
250
+
251
+ # Adds the header fields that were specified by the parent vault here
252
+ @vault.header_set.each do |field, type|
253
+ @dataset['header']["#{field}"] = nil if type == 'single'
254
+ @dataset['header']["#{field}"] = [] if type == 'list'
255
+ @dataset['header']["#{field}"] = {} if type == 'tree'
256
+ end
257
+
258
+ @dataset['body'] = {}
259
+ # @version.update
260
+ @dataset['body']["#{@version}"] = {}
261
+ # @dataset['body']["#{@version}"]['name'] = @name
262
+ end
263
+
264
+ def construct_path(name, vault)
265
+ @path = "#{vault.path}/data/#{SecurityUtils::tiger_hash(@name)}.ree"
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,34 @@
1
+ # ====================================================
2
+ # Copyright 2015 Random Robot Softworks (see @author)
3
+ # @author: Katharina Sabel | www.2rsoftworks.de
4
+ #
5
+ # Distributed under the GNU Lesser GPL Version 3
6
+ # (See accompanying LICENSE file or get a copy at
7
+ # https://www.gnu.org/licenses/lgpl.html)
8
+ # ====================================================
9
+
10
+ # These have to do with tokens being old, bad or missing.
11
+
12
+ class DaemonError < StandardError
13
+ end
14
+
15
+ class VaultAlreadyScopedError < DaemonError
16
+ end
17
+
18
+ class VaultNotScopedError < DaemonError
19
+ end
20
+
21
+ class VaultNotAvailableError < DaemonError
22
+ end
23
+
24
+ class UnknownTokenError < DaemonError
25
+ end
26
+
27
+ class UnautherisedTokenError < DaemonError
28
+ end
29
+
30
+ class MissingTokenError < DaemonError
31
+ end
32
+
33
+ class FunctionNotImplementedError < DaemonError
34
+ end
@@ -0,0 +1,29 @@
1
+ # ====================================================
2
+ # Copyright 2015 Random Robot Softworks (see @author)
3
+ # @author: Katharina Sabel | www.2rsoftworks.de
4
+ #
5
+ # Distributed under the GNU Lesser GPL Version 3
6
+ # (See accompanying LICENSE file or get a copy at
7
+ # https://www.gnu.org/licenses/lgpl.html)
8
+ # ====================================================
9
+
10
+ class EncryptionError < StandardError
11
+ end
12
+
13
+ class MissingEncryptionTypeError < EncryptionError
14
+ end
15
+
16
+ class MissingUserPasswordError < EncryptionError
17
+ end
18
+
19
+ class WrongUserPasswordError < EncryptionError
20
+ end
21
+
22
+ class InsecureUserPasswordError < EncryptionError
23
+ end
24
+
25
+ class EncryptionFailedError < EncryptionError
26
+ end
27
+
28
+ class DecryptionFailedError < EncryptionError
29
+ end
@@ -0,0 +1,39 @@
1
+ # ====================================================
2
+ # Copyright 2015 Random Robot Softworks (see @author)
3
+ # @author: Katharina Sabel | www.2rsoftworks.de
4
+ #
5
+ # Distributed under the GNU Lesser GPL Version 3
6
+ # (See accompanying LICENSE file or get a copy at
7
+ # https://www.gnu.org/licenses/lgpl.html)
8
+ # ====================================================
9
+
10
+ class VaultError < StandardError
11
+ end
12
+
13
+ class VaultExistsAtLocationError < VaultError
14
+ end
15
+
16
+ class VaultDoesNotExistError < VaultError
17
+ end
18
+
19
+ class VaultWritePermissionsError < VaultError
20
+ end
21
+
22
+ class VaultMissingConfigurationError < VaultError
23
+ end
24
+
25
+ class VaultLoggerError < VaultError
26
+ end
27
+
28
+ # This has been depreciated.
29
+ class BadCacheError < VaultError
30
+ end
31
+
32
+ class FileNotFoundError < VaultError
33
+ end
34
+
35
+ class FileBusyError < VaultError
36
+ end
37
+
38
+ class MalformedSearchError < VaultError
39
+ end
@@ -0,0 +1,547 @@
1
+ # ====================================================
2
+ # Copyright 2015 Lonely Robot (see @author)
3
+ # @author: Katharina Sabel | www.2rsoftworks.de
4
+ #
5
+ # Distributed under the GNU Lesser GPL Version 3
6
+ # (See accompanying LICENSE file or get a copy at
7
+ # https://www.gnu.org/licenses/lgpl.html)
8
+ # ====================================================
9
+
10
+ # => Vault internals
11
+ require_relative 'datafile'
12
+
13
+ # => Import custom errors
14
+ require_relative 'errors/encryption_errors'
15
+ require_relative 'errors/vault_errors'
16
+
17
+ # => Import utilities and tools
18
+ require_relative 'utils/utilities'
19
+ require_relative 'utils/sorting'
20
+ require_relative 'utils/logger'
21
+
22
+ # => Import security package classes
23
+ require_relative 'security/multifish'
24
+ require_relative 'security/twofish'
25
+ require_relative 'security/aes'
26
+
27
+ # => Import internals
28
+ require 'fileutils'
29
+ require 'socket'
30
+ require 'json'
31
+ require 'yaml'
32
+ require 'etc'
33
+
34
+ module Reedb
35
+ class ReeVault
36
+
37
+ attr_reader :path
38
+
39
+ # Encryption handler to be used by the vault files
40
+ #
41
+ attr_reader :crypt
42
+
43
+ # Indicates the size of the vault as per data file entries.
44
+ # Is updated with every header cache and write cycle.
45
+ #
46
+ attr_reader :size
47
+
48
+ # Holds a hash of possible values that header files in this vault
49
+ # can have. Fields need to be specified by name and a type. To choose
50
+ # from 'single', 'list' and 'tree' (var, list, dict)
51
+ #
52
+ attr_reader :header_set
53
+
54
+ # Constructor for a vault with name, path and encryption enum.
55
+ # Valid encryption parameters are :aes, :twofish, :multi and :auto_fill
56
+ #
57
+ def initialize(name, path, encprytion)
58
+ @already_logging = false
59
+
60
+ # Header maps
61
+ @headers = {}
62
+ @hgroups = {}
63
+
64
+ # Fileset
65
+ @locked = false
66
+ @locks = []
67
+
68
+ # Defines the default (and boring vanilla) header set
69
+ @header_set = {
70
+ 'urls'=>'list',
71
+ 'tags'=>'list'
72
+ }
73
+
74
+ construct_path("#{name}", "#{path}")
75
+ init_encryption(encprytion) # => Throws exceptions!
76
+ self.secure_config(false)
77
+ return self
78
+ end
79
+
80
+ def secure_config(boolean = true)
81
+ @secure_config = boolean
82
+ return self
83
+ end
84
+
85
+ # Fields can be added to a vault header BUT NOT REMOVED AGAIN!
86
+ # So be careful what you put in your header.
87
+ # (aka upgrade yes, downgrade noooo)
88
+ #
89
+ # Unused fields can remain blank but need to stay in a vaults header list
90
+ # for backwards compatiblity
91
+ #
92
+ def add_header_field(name, type)
93
+ @header_set[name] = type unless @header_set[name]
94
+ end
95
+
96
+
97
+ # Pokes if a vault exists
98
+ def try?
99
+ return self.includes?('config')
100
+ end
101
+
102
+ # Counts the vault contents and returns an Integer
103
+ # WITHOUT HAVING TO UNLOCK THE VAULT!
104
+ #
105
+ def count
106
+ counter = 0
107
+ Dir.glob("#{@path}/data/*.ree") do |f|
108
+ counter += 1
109
+ end
110
+ return counter
111
+ end
112
+
113
+ # Little helper method to determine if a vault is in the middle of
114
+ # a write cycle. Which would cause horrible crashes on other applications
115
+ # and errors on the file system if things are moved around inside
116
+ #
117
+ def locked?() return @locked end
118
+
119
+ def create(password = :failed)
120
+ # If keygen was used to set a user password then fetch it
121
+ # and remove the variable from memory!
122
+ return nil unless password?(password)
123
+ return nil unless encryption?(password)
124
+
125
+ # puts "This is the password: #{password}"
126
+
127
+ # => Encryption now active and key available under @crypt.key
128
+ @conf_path = "#{@path}/config"
129
+
130
+ needs_creation = true
131
+
132
+ if self.includes?('config')
133
+ raise VaultExistsAtLocationError.new, "Vault already exists at location #{@path}. Aborting operation..."
134
+
135
+ # => This rules out lots of code to be run
136
+ needs_creation = false
137
+ else
138
+ if Reedb::archos == :linux || Reedb::archos == :osx
139
+ FileUtils::mkdir_p(File.expand_path("#{@path}/data")) # => Data dir
140
+ FileUtils::mkdir(File.expand_path("#{@path}/shasums")) # => Checksum dir
141
+ FileUtils::mkdir(File.expand_path("#{@path}/logs")) # => Logs dir
142
+
143
+ FileUtils::chmod_R(0744, "#{@path}")
144
+
145
+ # This is used for windows because windows fucking sucks!
146
+ else
147
+ FileUtils::mkdir_p(File.expand_path("#{@path}/data")) # => Data dir
148
+ FileUtils::mkdir(File.expand_path("#{@path}/shasums")) # => Checksum dir
149
+ FileUtils::mkdir(File.expand_path("#{@path}/logs")) # => Logs dir
150
+ end
151
+ end
152
+
153
+ # Now that the vault directory exists logs can be opened.
154
+ init_logger(true)
155
+
156
+ if needs_creation
157
+ # Code that will only be run if the vault was just created on the system
158
+ time = Reedb::Utilities.get_time(false)
159
+ VaultLogger.write("Vault created on #{time}. All directories created successfully!")
160
+
161
+ # => Now creating configuration file
162
+ @config = {}
163
+ @config['vault_name'] = "#{@name}"
164
+ @config['creation_date'] = "#{Utilities.get_time}"
165
+ @config['last_updated'] = "#{Utilities.get_time}"
166
+ @config['creation_machine'] = "#{Socket.gethostname}"
167
+ @config['updating_machine'] = "#{Socket.gethostname}"
168
+ @config['creation_user'] = "#{Etc.getlogin}"
169
+ @config['updating_user'] = "#{Etc.getlogin}"
170
+ @config['header_set'] = "#{@header_set}"
171
+ @config['creation_version'] = "#{Reedb::VERSION}"
172
+ save_config
173
+
174
+ # Now writing encrypted key to file with ASCII armour
175
+ update_secure_info("cey", @encrypted_key)
176
+ # remove_instance_variable(:@encrypted_key)
177
+ end
178
+ self.load("#{password}")
179
+ end
180
+
181
+ def load(password)
182
+ unless self.includes?('config')
183
+ raise VaultDoesNotExistError.new("Loading vault failed because it couldn't be found at the specified path!")
184
+ end
185
+ init_logger(false)
186
+
187
+ # Check if the config needs to be read via ASCII64 or YAML
188
+ if self.includes?('pom')
189
+ # Config is stored with ASCII Armour
190
+ @config = read_secure_info('config')
191
+ else
192
+ @config = YAML.load_file("#{@path}/config")
193
+ end
194
+
195
+
196
+
197
+ return nil unless unlock_vault("#{password}")
198
+ VaultLogger.write("Finished loading vault", 'debug')
199
+ cache_headers
200
+
201
+ return self
202
+ end
203
+
204
+ # Read a single file from the vault in secure mode
205
+ # Returns the entire file or only it's current set in hashes.
206
+ #
207
+ def read_file(name, history = false)
208
+
209
+ # Loads the file data into a local variable if it exists
210
+ file_data = load_file_data(name)
211
+ if file_data == nil
212
+ raise FileNotFoundError.new("#{name} could not be read: File not found!")
213
+ return VAULT_FILE_NOT_FOUND_ERROR # If the exception isn't handled correctly
214
+ else
215
+ # This code is executed if the file was found (thus data is in file_data)
216
+ compiled = {}
217
+ compiled['header'] = {}
218
+
219
+ # Removes the latest version from the header because it is insignificant.
220
+ file_data['header'].each do |key, value|
221
+ compiled['header']["#{key}"] = value unless key == "latest"
222
+ end
223
+
224
+ if history
225
+ compiled['body'] = file_data['body']
226
+ else
227
+ body_list = []
228
+ file_data['body'].each do |key, value|
229
+ body_list << key
230
+ end
231
+
232
+ compiled['body'] = {}
233
+
234
+ # Now sort the list of body versions
235
+ body_list.heapsort!
236
+ body_list.each do |version|
237
+ file_data['body']["#{version}"].each do |key, value|
238
+ compiled['body']["#{key}"] = value
239
+ end
240
+ end
241
+ end
242
+
243
+ # Then return that value
244
+ return compiled
245
+ end
246
+ end
247
+
248
+ # Check the file API or the wiki to learn how this function works.
249
+ # This function is also used to delete fields from header space.
250
+ #
251
+ def update(name, data)
252
+ cache_headers # Cache headers first to be sure
253
+
254
+ (raise FileBusyError.new, "File #{name} busy" ; return) if @locks.include?(name)
255
+ @locks << name
256
+
257
+ if @headers.key?(name)
258
+ # Creates file object from existing file object.
259
+ df = DataFile.new(name, self, load_file_data(name, :secure))
260
+ df.insertv2(data, :hard) # Default cache mode
261
+ else
262
+ df = DataFile.new(name, self)
263
+ df.insertv2(data, :hard) # Default cache mode
264
+ end
265
+
266
+ # => Make sure that everything is up to date.
267
+ cache_headers
268
+ @config['updating_user'] = "#{Etc.getlogin}"
269
+ @config['updating_machine'] = "#{Socket.gethostname}"
270
+ @config['last_updated'] = "#{Utilities.get_time}"
271
+ @config['last_version'] = "#{Reedb::VERSION}"
272
+ save_config
273
+
274
+ # Sync and close the file.
275
+ df.sync.close
276
+
277
+ # Unlocks the file again for other processes to edit.
278
+ @locks.delete(name)
279
+ end
280
+
281
+ def remove_file name
282
+ path_to_file = load_file_hash(name)
283
+ if path_to_file
284
+ FileUtils.rm(path_to_file)
285
+ puts "Successfullly removed file #{name}"
286
+ VaultLogger.write("Removed file #{name} from vault.", 'debug')
287
+ else
288
+ raise FileNotFoundError.new("#{name} could not be removed: File not found!")
289
+ end
290
+ end
291
+
292
+ # Returns headers according to a search queury
293
+ #
294
+ # { 'name' => '__name__',
295
+ # 'url' => '__url__',
296
+ # 'tags' => '__tags__',
297
+ # 'generic_field' => '__generic_information__'
298
+ # }
299
+ #
300
+ # 'tags=search engines, internet#urls=www.poodle.com'
301
+ #
302
+ def list_headers search
303
+ query = {}
304
+ cache_headers # => This fills @headers and @hfields
305
+
306
+ return @headers unless search
307
+
308
+ begin
309
+ splat = search.split('#')
310
+ splat.each do |target|
311
+ slice = target.split('=')
312
+ query["#{slice[0]}"] = slice[1..-1]
313
+ end
314
+ # puts query
315
+ rescue
316
+ raise MalformedSearchError.new, "Malformed search data"
317
+ end
318
+
319
+ log_query = {}
320
+ candidates = []
321
+
322
+ query.each do |cat, data|
323
+ data.each do |val|
324
+ log_query["#{cat}"] = @hgroups["#{cat}"]["#{val}"] if @hgroups["#{cat}"].include?(val)
325
+ log_query["#{cat}"].each { |c| candidates << c unless candidates.include?(c) }
326
+ end
327
+ end
328
+ return_buffer = candidates
329
+ candidates.each do |can|
330
+ log_query.each do |cat, data|
331
+ return_buffer.delete(can) unless log_query["#{cat}"].include?(can)
332
+ end
333
+ end
334
+
335
+ return return_buffer
336
+ end
337
+
338
+ # Dump headers and files from memory in times of
339
+ # inactivity for security reasons
340
+ def unload
341
+ remove_instance_variable(:@headers)
342
+ @headers = {}
343
+
344
+ VaultLogger.write("It has been $TIME since the last interaction. Unloading vault contents for security reasons.", 'debug')
345
+ end
346
+
347
+ def close
348
+ VaultLogger.write("Scheduled closing of the vault.", 'debug')
349
+ @crypt.stop_encryption if @crypt.init
350
+
351
+ # Removing class variables for cleanup
352
+ remove_instance_variable(:@crypt)
353
+ remove_instance_variable(:@headers)
354
+ end
355
+
356
+ # Quickly returns if a file exists in the vault or it's children.
357
+ def includes? file
358
+ return File.exists?("#{@path}/#{file}")
359
+ end
360
+
361
+ def to_s
362
+ return "Vault: #{@name}, Path: #{@path}, File count: #{@headers.length}"
363
+ end
364
+
365
+ private
366
+
367
+ # Caches the current set of headers on a vault.
368
+ #
369
+ def cache_headers
370
+ @headers = {}
371
+
372
+ VaultLogger.write("Starting a cache cycle.", 'debug')
373
+
374
+ Dir.glob("#{@path}/data/*.ree") do |file|
375
+ f = File.open(file, 'r')
376
+ encrypted = Base64.decode64(f.read)
377
+ decrypted = @crypt.decrypt(encrypted)
378
+
379
+ data = JSON.parse(decrypted)
380
+ df = DataFile.new(nil, self, data)
381
+
382
+ tmp_head = df.cache(:header)
383
+ tmp_name = df.name
384
+
385
+ # Blank the df variable just in case.
386
+ df = 0x111111111111111
387
+
388
+ @headers[tmp_name] = tmp_head
389
+
390
+ # Now work with the header set to determine sub-groups
391
+ tmp_head.each do |category, data|
392
+
393
+ # This will loop through all the category groups in the
394
+ # header that have been registered
395
+ if @header_set.include?(category)
396
+
397
+ # Creates a new sub-hash for data
398
+ @hgroups["#{category}"] = {} unless @hgroups["#{category}"]
399
+
400
+ if @header_set["#{category}"] == 'list'
401
+ data.each do |target|
402
+ @hgroups["#{category}"]["#{target}"] = [] unless @hgroups["#{category}"]["#{target}"]
403
+ @hgroups["#{category}"]["#{target}"] << tmp_name unless @hgroups["#{category}"]["#{target}"].include?(tmp_name)
404
+ end
405
+
406
+ elsif @header_set["#{category}"] == 'single'
407
+ @hgroups["#{category}"] = "#{data}"
408
+ end
409
+
410
+ #TODO: Implement dictionary head later
411
+ end
412
+ end
413
+ end
414
+ end
415
+
416
+ def load_file_hash name
417
+ cache_headers
418
+ if @headers.key?(name)
419
+ name_hash = SecurityUtils::tiger_hash("#{name}")
420
+ return "#{@path}/data/#{name_hash}.ree"
421
+ else
422
+ return false
423
+ end
424
+ end
425
+
426
+ # Loads a file with the clear name from headers.
427
+ # If file isn't found in headers vault is recached
428
+ # and file is then loaded from headers.
429
+ #
430
+ # If file isn't found in headers error is output.
431
+ #
432
+ def load_file_data(name, mode = :secure)
433
+ cache_headers
434
+ VaultLogger.write("Loading file #{name} from vault", 'debug')
435
+ if @headers.key?(name)
436
+ name_hash = SecurityUtils::tiger_hash("#{name}")
437
+ file_path = "#{@path}/data/#{name_hash}.ree"
438
+ f = File.open(file_path, 'r')
439
+ encrypted = Base64.decode64(f.read)
440
+ decrypted = @crypt.decrypt(encrypted)
441
+
442
+ return JSON.parse(decrypted) if mode == :secure
443
+ else
444
+ return nil
445
+ end
446
+ end
447
+
448
+ def save_config
449
+ @conf_path = "#{@path}/config"
450
+ if @secure_config
451
+ update_secure_info('config', @config)
452
+ par_path = "#{@path}/pom"
453
+ msg = "Why are you reading this?"
454
+ File.open("#{par_path}", "wb").write(Base64.encode64("#{msg}"))
455
+ else
456
+ File.open("#{@conf_path}", "wb+"){ |f| YAML.dump(@config, f) }
457
+ end
458
+ end
459
+
460
+ # Builds the vault path from a path, name and trimming
461
+ # additional slashes from the end.
462
+ #
463
+ def construct_path(name, path)
464
+ (@name = name ; @path = "")
465
+ path.end_with?("/") ? @path = "#{path}#{name}.reevault" : @path = "#{path}/#{name}.reevault"
466
+ end
467
+
468
+ def update_secure_info(name, data = nil)
469
+ path = "#{@path}/#{name}"
470
+ File.open(path, "wb").write(Base64.encode64("#{data}"))
471
+ end
472
+
473
+ def read_secure_info name
474
+ path = "#{@path}/#{name}"
475
+ return Base64.decode64(File.open(path, "r").read())
476
+ end
477
+
478
+ def init_logger bool
479
+ begin
480
+ unless logger?(bool)
481
+ raise VaultLoggerError.new, "Logger failed to be initialised"
482
+ end
483
+ rescue VaultError => e
484
+ puts e.message
485
+ end
486
+ end
487
+
488
+ def logger? bool
489
+ (return false) if @already_logging && bool
490
+
491
+ VaultLogger.setup("#{@path}")
492
+ (@already_logging = true ; return true)
493
+ end
494
+
495
+ def password? password
496
+ raise MissingUserPasswordError.new, "Encryption error: Missing user password!" if password == nil
497
+
498
+ raise InsecureUserPasswordError.new, "Encryption error: Password too short! See: https://xkcd.com/936/" if password.length < Reedb::passlength
499
+
500
+ return true
501
+ end
502
+
503
+ def encryption? password
504
+ begin
505
+ @encrypted_key = @crypt.start_encryption(password)
506
+ rescue EncryptionError => e
507
+ puts e.message
508
+ return false
509
+ end
510
+ return true
511
+ end
512
+
513
+ # Unlocks the vault by decrypting the key and loading it into memory
514
+ # Enables the cryptographic module to decrypt and encrypt files.
515
+ #
516
+ def unlock_vault pw
517
+ begin
518
+ @encrypted_key = read_secure_info('cey') unless @encrypted_key
519
+ @crypt.start_encryption(pw, @encrypted_key)
520
+ remove_instance_variable(:@encrypted_key) if @encrypted_key
521
+ rescue #TODO: Check exception type here.
522
+ raise WrongUserPasswordError.new, "Incorrect user passphrase. Could not unlock!"
523
+ end
524
+
525
+ # Return values for the rest of the file.
526
+ return @crypt.init
527
+ end
528
+
529
+ # This method checks what encryption to use by enums.
530
+ # This can throw an exception if something was parsed incorrectly
531
+ # After this call the @crypt object has been initialised.
532
+ #
533
+ def init_encryption type
534
+ type = :aes if type == :auto
535
+ if type == :aes
536
+ @crypt = Reedb::RAES.new
537
+ elsif type == :twofish
538
+ @crypt = Reedb::Fish.new
539
+ elsif type == :multi
540
+ @crypt = Reedb::MLE.new
541
+ else
542
+ raise MissingEncryptionTypeError.new, "Encryption failed: Missing type. Aborting..."
543
+ end
544
+ end
545
+
546
+ end # class close
547
+ end # module close