reedb 0.10.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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