safedb 0.3.1011 → 0.4.1002

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +56 -19
  3. data/README.md +15 -15
  4. data/Rakefile +7 -0
  5. data/bin/safe +2 -2
  6. data/lib/{interprete.rb → cli.rb} +168 -121
  7. data/lib/controller/admin/README.md +47 -0
  8. data/lib/controller/admin/access.rb +47 -0
  9. data/lib/controller/admin/checkin.rb +83 -0
  10. data/lib/controller/admin/checkout.rb +57 -0
  11. data/lib/controller/admin/diff.rb +75 -0
  12. data/lib/{usecase → controller/admin}/export.rb +15 -14
  13. data/lib/controller/admin/goto.rb +52 -0
  14. data/lib/controller/admin/import.rb +54 -0
  15. data/lib/controller/admin/init.rb +113 -0
  16. data/lib/controller/admin/login.rb +88 -0
  17. data/lib/{usecase → controller/admin}/logout.rb +0 -0
  18. data/lib/controller/admin/open.rb +39 -0
  19. data/lib/{usecase → controller/admin}/token.rb +2 -2
  20. data/lib/controller/admin/tree.md +54 -0
  21. data/lib/{usecase → controller/admin}/use.rb +0 -0
  22. data/lib/controller/admin/view.rb +61 -0
  23. data/lib/{usecase → controller/api}/docker/README.md +0 -0
  24. data/lib/{usecase → controller/api}/docker/docker.rb +1 -1
  25. data/lib/{usecase → controller/api}/jenkins/README.md +0 -0
  26. data/lib/{usecase → controller/api}/jenkins/jenkins.rb +1 -1
  27. data/lib/{usecase → controller/api}/terraform/README.md +1 -1
  28. data/lib/{usecase → controller/api}/terraform/terraform.rb +1 -1
  29. data/lib/{usecase → controller/api}/vpn/README.md +1 -1
  30. data/lib/{usecase → controller/api}/vpn/vpn.ini +0 -0
  31. data/lib/{usecase → controller/api}/vpn/vpn.rb +0 -0
  32. data/lib/{usecase → controller}/config/README.md +0 -0
  33. data/lib/{usecase → controller}/edit/README.md +0 -0
  34. data/lib/controller/edit/editverse.rb +48 -0
  35. data/lib/controller/edit/put.rb +35 -0
  36. data/lib/controller/edit/remove.rb +29 -0
  37. data/lib/{usecase/update/README.md → controller/edit/rename.md} +0 -0
  38. data/lib/{usecase → controller}/files/README.md +1 -1
  39. data/lib/controller/files/read.rb +36 -0
  40. data/lib/{usecase/files/eject.rb → controller/files/write.rb} +15 -20
  41. data/lib/{usecase → controller}/id.rb +0 -0
  42. data/lib/controller/query/print.rb +26 -0
  43. data/lib/controller/query/queryverse.rb +39 -0
  44. data/lib/controller/query/show.rb +50 -0
  45. data/lib/{session/require.gem.rb → controller/requirer.rb} +13 -9
  46. data/lib/{usecase → controller}/set.rb +4 -4
  47. data/lib/controller/usecase.rb +244 -0
  48. data/lib/{usecase → controller}/verse.rb +0 -0
  49. data/lib/{usecase → controller}/visit/README.md +0 -0
  50. data/lib/{usecase → controller}/visit/visit.rb +0 -0
  51. data/lib/factbase/facts.safedb.net.ini +7 -7
  52. data/lib/{keytools/key.docs.rb → model/README.md} +102 -66
  53. data/lib/model/book.rb +484 -0
  54. data/lib/model/branch.rb +48 -0
  55. data/lib/model/checkin.feature +33 -0
  56. data/lib/{configs/README.md → model/configs.md} +4 -4
  57. data/lib/model/content.rb +214 -0
  58. data/lib/model/indices.rb +132 -0
  59. data/lib/model/safe_tree.rb +51 -0
  60. data/lib/model/state.inspect.rb +221 -0
  61. data/lib/model/state.migrate.rb +334 -0
  62. data/lib/model/text_chunk.rb +68 -0
  63. data/lib/{extension → utils/extend}/array.rb +0 -0
  64. data/lib/{extension → utils/extend}/dir.rb +0 -0
  65. data/lib/{extension → utils/extend}/file.rb +0 -0
  66. data/lib/utils/extend/hash.rb +76 -0
  67. data/lib/{extension → utils/extend}/string.rb +6 -6
  68. data/lib/{session/fact.finder.rb → utils/facts/fact.rb} +0 -0
  69. data/lib/utils/identity/identifier.rb +356 -0
  70. data/lib/{keytools/key.ident.rb → utils/identity/machine.id.rb} +67 -4
  71. data/lib/utils/inspect/inspector.rb +81 -0
  72. data/lib/{keytools/kdf.bcrypt.rb → utils/kdfs/bcrypt.rb} +0 -0
  73. data/lib/{keytools → utils/kdfs}/kdf.api.rb +16 -16
  74. data/lib/{keytools/key.local.rb → utils/kdfs/kdfs.rb} +40 -40
  75. data/lib/{keytools/kdf.pbkdf2.rb → utils/kdfs/pbkdf2.rb} +0 -0
  76. data/lib/{keytools/kdf.scrypt.rb → utils/kdfs/scrypt.rb} +0 -0
  77. data/lib/{keytools → utils}/key.error.rb +2 -2
  78. data/lib/{keytools → utils}/key.pass.rb +2 -2
  79. data/lib/{keytools → utils/keys}/key.64.rb +0 -0
  80. data/lib/{keytools → utils/keys}/key.rb +6 -2
  81. data/lib/{keytools/key.iv.rb → utils/keys/random.iv.rb} +0 -0
  82. data/lib/{logging/gem.logging.rb → utils/logs/logger.rb} +6 -5
  83. data/lib/{keytools/key.pair.rb → utils/store/datamap.rb} +48 -30
  84. data/lib/{keytools/key.db.rb → utils/store/datastore.rb} +38 -104
  85. data/lib/utils/store/merge-boys-school.json +40 -0
  86. data/lib/utils/store/merge-girls-school.json +48 -0
  87. data/lib/utils/store/merge-merged-data.json +56 -0
  88. data/lib/utils/store/struct.rb +75 -0
  89. data/lib/utils/store/test-commands.sh +24 -0
  90. data/lib/{keytools/key.now.rb → utils/time/timestamp.rb} +32 -21
  91. data/lib/version.rb +1 -1
  92. metadata +86 -73
  93. data/lib/extension/hash.rb +0 -33
  94. data/lib/keytools/key.algo.rb +0 -109
  95. data/lib/keytools/key.api.rb +0 -1326
  96. data/lib/keytools/key.id.rb +0 -322
  97. data/lib/modules/cryptology/amalgam.rb +0 -70
  98. data/lib/modules/cryptology/engineer.rb +0 -99
  99. data/lib/modules/mappers/dictionary.rb +0 -288
  100. data/lib/session/time.stamp.rb +0 -340
  101. data/lib/session/user.home.rb +0 -49
  102. data/lib/usecase/cmd.rb +0 -471
  103. data/lib/usecase/edit/delete.rb +0 -46
  104. data/lib/usecase/files/file_me.rb +0 -78
  105. data/lib/usecase/files/read.rb +0 -169
  106. data/lib/usecase/files/write.rb +0 -89
  107. data/lib/usecase/goto.rb +0 -57
  108. data/lib/usecase/import.rb +0 -157
  109. data/lib/usecase/init.rb +0 -61
  110. data/lib/usecase/login.rb +0 -72
  111. data/lib/usecase/open.rb +0 -71
  112. data/lib/usecase/print.rb +0 -40
  113. data/lib/usecase/put.rb +0 -81
  114. data/lib/usecase/show.rb +0 -138
  115. data/lib/usecase/update/rename.rb +0 -180
  116. data/lib/usecase/view.rb +0 -71
@@ -1,49 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- # coding: utf-8
4
-
5
- # opensession contains basic behaviour for managing a client only
6
- # (serverless) session. Configuration directives are read and written
7
- # from an INI off the home directory that is created when the session
8
- # is first initiated.
9
- module OpenSession
10
-
11
- # This singleton class ascertains the users home folder in a manner
12
- # agnositic to whether the software is running on Linux or Windows.
13
- class Home
14
- include Singleton
15
-
16
- # This static behaviour reads the [home folder] just once.
17
- def self.dir
18
- return Home.instance.folder
19
- end
20
-
21
- # This static behaviour reads the [username] just once.
22
- def self.usr
23
- return Home.instance.username
24
- end
25
-
26
- attr_reader :folder
27
- attr_reader :username
28
-
29
- # Ascertain the home folder location.
30
- def initialize
31
-
32
- # On Windows the home folder may end with [AppData/Roaming].
33
- extraneous_path = "/AppData/Roaming"
34
-
35
- @folder = Dir.home
36
- @username = @folder.split("/").last
37
- return unless Dir.home.end_with? extraneous_path
38
-
39
- # Remove the tail [AppData/Roaming] from the home path.
40
- @folder = Dir.home.gsub extraneous_path, ""
41
- @username = @folder.split("/").last
42
-
43
- end
44
-
45
-
46
- end
47
-
48
-
49
- end
data/lib/usecase/cmd.rb DELETED
@@ -1,471 +0,0 @@
1
- #!/usr/bin/ruby
2
- # coding: utf-8
3
-
4
- module SafeDb
5
-
6
- # The parent SafeDb use case is designed to be extended by the cli
7
- # (command line) use cases like {SafeDb::Open}, {SafeDb::Put} and
8
- # {SafeDb::Lock} because it describes behaviour common to at least two
9
- # (but usually more) of the use cases.
10
- #
11
- # == Common Use Case Behaviour
12
- #
13
- # This {SafeDb::UseCase} use case is designed to be extended and does preparatory
14
- # work to create favourable and useful conditions to make use cases readable,
15
- # less repetitive, simpler and concise.
16
- #
17
- # == Machine (Workstation) Configuration File
18
- #
19
- # The global configuration filepath is found off the home directory using {Dir.home}.
20
- #
21
- # ~/.safedb.net/safedb.net.configuration.ini
22
- #
23
- # The global configuration file in INI format is managed through the methods
24
- #
25
- # - {grab} read the value at key_name from the default section
26
- # - {stash} put directive key/value pair in default section
27
- # - {read} read the value at key_name from the parameter section
28
- # - {write} put directive key/value pair in parameter section
29
- class UseCase
30
-
31
- # This prefix denotes keys and their values should be posted as environment
32
- # variables within the context (for example terraform) before instigating the
33
- # main action like terraform apply.
34
- ENV_VAR_PREFIX_A = "evar."
35
- ENV_VAR_PREFIX_B = "@evar."
36
-
37
- # This prefix precedes keynames whose map value represents a file object.
38
- FILE_KEY_PREFIX = "file::"
39
-
40
- # The base64 encoded representation of the file content is placed into
41
- # a map with this keyname.
42
- FILE_CONTENT_KEY = "content64"
43
-
44
- # The file base name is placed into a map with this keyname.
45
- FILE_NAME_KEY = "filename"
46
-
47
-
48
- # This is the root command typed into the shell to invoke one of the
49
- # safe use cases.
50
- COMMANDMENT = "safe"
51
-
52
-
53
- # The name of the environment variable that will hold the session token
54
- # generated by {self.generate_session_token}. This environment variable
55
- # is typically instantiated either manually (for ad-hoc use) or through
56
- # media such as dot profile.
57
- ENV_VAR_KEY_NAME = "SAFE_TTY_TOKEN"
58
-
59
-
60
- # If and when this command line credentials management app needs to write
61
- # any configuration directives to the machine's userspace it will use this
62
- # constant to denote the (off-home) directory name.
63
- APP_DIR_NAME = "safedb.net"
64
-
65
-
66
- # Get the master database. This behaviour can only complete
67
- # correctly if a successful login precedes this call either
68
- # in this or an ancestral shell environment.
69
- #
70
- # @return [Hash]
71
- # the hash data structure returned represents the master
72
- # database.
73
- def get_master_database
74
-
75
- begin
76
-
77
- log.info(x) { "Request issued to read the master database." }
78
- return KeyApi.read_master_db()
79
-
80
- rescue OpenSSL::Cipher::CipherError => e
81
-
82
- log.fatal(x) { "Exception getting master db for the safe book." }
83
- log.fatal(x) { "The exception message is ~> [[ #{e.message} ]]" }
84
- e.backtrace.log_lines
85
- abort e.message
86
-
87
- end
88
-
89
- end
90
-
91
- # The path to the initial configuration file below the user's home
92
- # directory. The directory name the configuration file sits in is
93
- # a dot prefixed context name derived from the value inside the
94
- # {APP_DIR_NAME} constant.
95
- #
96
- # ~/.<<context-name>>/<<context-name>>-configuration.ini
97
- #
98
- # You can see the filename too is derived from the context with a
99
- # trailing string ending in <b>.ini</b>.
100
- #
101
- # @return [String] full path to the context configuration file
102
- def config_file
103
- return File.join config_directory(), "#{APP_DIR_NAME}.configuration.ini"
104
- end
105
-
106
-
107
- # This method returns the absolute path to the directory that the
108
- # configuration file sits in. It is basically just the dot prefixed
109
- # context name (the {APP_DIR_NAME} constant).
110
- #
111
- # ~/.<<context-name>>
112
- #
113
- # @return [String] path to directory holding context configuration file
114
- def config_directory
115
- return File.join( Dir.home, ".#{APP_DIR_NAME}" )
116
- end
117
-
118
-
119
- # Execute the use cases's flow from beginning when
120
- # you validate the input and parameters through the
121
- # memorize, execute and the final cleanup.
122
- def flow_of_events
123
-
124
- check_pre_conditions
125
- execute
126
- cleanup
127
- check_post_conditions
128
-
129
- end
130
-
131
-
132
- # Validate the input parameters and check that the current
133
- # state is perfect for executing the use case.
134
- #
135
- # If either of the above fail - the validation function should
136
- # set a human readable string and then throw an exception.
137
- def check_pre_conditions
138
-
139
- begin
140
-
141
- pre_validation
142
-
143
- rescue OpenError::CliError => e
144
-
145
- puts ""
146
- puts "Your command did not complete successfully."
147
- puts "Pre validation checks failed."
148
- puts ""
149
- puts " => #{e.message}"
150
- puts ""
151
- abort e.message
152
- end
153
-
154
- end
155
-
156
-
157
- # Override me if you need to
158
- def pre_validation
159
-
160
- end
161
-
162
-
163
- # After the main flow of events certain state conditions
164
- # must hold true thus demonstrating that the observable
165
- # value has indeed ben delivered.
166
- #
167
- # Child classes should subclass this method and place any
168
- # post execution (post condition) checks in it and then
169
- # make a call to this method through the "super" keyword.
170
- def check_post_conditions
171
-
172
- begin
173
-
174
- post_validation
175
-
176
- rescue OpenError::CliError => e
177
-
178
- puts ""
179
- puts "Your command did not complete successfully."
180
- puts "Post validation checks failed."
181
- puts ""
182
- puts " => #{e.message}"
183
- #### puts " => #{e.culprit}"
184
- puts ""
185
- abort e.message
186
- end
187
-
188
- end
189
-
190
-
191
- # Child classes should subclass this method and place any
192
- # post execution (post condition) checks in it and then
193
- # make a call to this method through the "super" keyword if
194
- # this method gets any global behaviour in it worth calling.
195
- def post_validation
196
-
197
- end
198
-
199
-
200
- # Execute the main flow of events of the use case. Any
201
- # exceptions thrown are captured and if the instance
202
- # variale [@human_readable_message] is set - tell the
203
- # user about it. Without any message - just tell the
204
- # user something went wrong and tell them where the logs
205
- # are that might carry more information.
206
- def execute
207
-
208
- end
209
-
210
-
211
- # If the use case validation went well, the memorization
212
- # went well the
213
- def cleanup
214
-
215
- end
216
-
217
-
218
- # This use case is initialized primary by resolving the configured
219
- # +general and use case specific facts+. To access the general facts,
220
- # a domain name is expected in the parameter delegated by the extension
221
- # use case classes.
222
- def initialize
223
-
224
- class_name = self.class.name.split(":").last.downcase
225
- is_no_token_usecase = [ "token", "init", "id" ].include? class_name
226
- return if is_no_token_usecase
227
-
228
- exit(100) unless ops_key_exists?
229
-
230
- fact_filepath = File.sister_filepath( self, "ini", :execute )
231
- log.info(x) { "Search location for INI factfile is [#{fact_filepath}]" }
232
- return unless File.exists?( fact_filepath )
233
-
234
- @facts = FactFind.new()
235
- add_secret_facts @facts
236
- @facts.assimilate_ini_file( fact_filepath )
237
- @dictionary = @facts.f[ @facts.to_symbol( class_name ) ]
238
-
239
- end
240
-
241
-
242
- private
243
-
244
-
245
- ENV_PATH = "env.path"
246
- KEY_PATH = "key.path"
247
- ENVELOPE_KEY_PREFIX = "envelope@"
248
-
249
- LAST_ACCESSED = "last.accessed.time"
250
-
251
- SESSION_DICT_LOCK_SIZE = 32
252
-
253
- SESSION_DICT_LOCK_NAME = "crypted.session.dict.lock"
254
-
255
- ENVELOPE_KEY_SIZE = 32
256
-
257
- ENVELOPE_KEY_NAME = "crypted.envelope.key"
258
-
259
- ENVELOPE_ID_SIZE = 16
260
-
261
- ENVELOPE_ID_NAME = "crypted.envelope.id"
262
-
263
- SESSION_ID_SIZE = 64
264
-
265
- SESSION_FILENAME_ID_SIZE = 24
266
-
267
- SESSION_START_TIMESTAMP_NAME = "session.creation.time"
268
-
269
- MASTER_LOCK_KEY_NAME = "master.session.lock.key"
270
-
271
- APPLICATION_GEM_NAME = "safedb.net"
272
- APPLICATION_GEM_WEBSITE = "https://www.safedb.net"
273
- APPLICATION_GITHUB_URL = "https://github.com/devops4me/safedb.net"
274
-
275
-
276
- def add_secret_facts fact_db
277
-
278
- master_db = get_master_database()
279
- raise ArgumentError.new "There is no open chapter here." if unopened_envelope?( master_db )
280
- chapter_id = ENVELOPE_KEY_PREFIX + master_db[ ENV_PATH ]
281
- verse_id = master_db[ KEY_PATH ]
282
- chapter_data = KeyDb.from_json( KeyApi.content_unlock( master_db[ chapter_id ] ) )
283
- mini_dictionary = chapter_data[ master_db[ KEY_PATH ] ]
284
-
285
- mini_dictionary.each do | key_str, value_str|
286
- fact_db.assimilate_fact( "secrets", key_str, value_str )
287
- end
288
-
289
- end
290
-
291
-
292
- def create_header()
293
-
294
- return KeyApi.format_header(
295
- SafeDb::VERSION,
296
- APPLICATION_GEM_NAME,
297
- APPLICATION_GITHUB_URL,
298
- @domain_name
299
- )
300
-
301
- end
302
-
303
-
304
-
305
- def ops_key_exists?
306
-
307
- log_env()
308
-
309
- if ( ENV.has_key? ENV_VAR_KEY_NAME )
310
- return true
311
- end
312
-
313
- puts ""
314
- puts "safe needs you to create a session key."
315
- puts "To automate this step see the documentation."
316
- puts "To create the key run the below command."
317
- puts ""
318
- puts " export #{ENV_VAR_KEY_NAME}=`#{COMMANDMENT} token`"
319
- puts ""
320
- puts "Those are backticks surrounding `#{COMMANDMENT} token`"
321
- puts "Not apostrophes."
322
- puts ""
323
-
324
- return false
325
-
326
- end
327
-
328
-
329
- def log_env()
330
-
331
- log.info(x) { "Gem Root Folder => #{Gem.dir()}" }
332
- log.info(x) { "Gem Config File => #{Gem.config_file()}" }
333
- log.info(x) { "Gem Binary Path => #{Gem.default_bindir()}" }
334
- log.info(x) { "Gem Host Path => #{Gem.host()}" }
335
- log.info(x) { "Gem Caller Folder => #{Gem.location_of_caller()}" }
336
- log.info(x) { "Gem Paths List => #{Gem.path()}" }
337
- log.info(x) { "Gem Platforms => #{Gem.platforms()}" }
338
- log.info(x) { "Gem Ruby Version X => #{Gem.ruby()}" }
339
- log.info(x) { "Gem Ruby Version Y => #{Gem::VERSION}" }
340
- log.info(x) { "Gem Ruby Version Z => #{Gem.latest_rubygems_version()}" }
341
- log.info(x) { "Gem User Folder => #{Gem.user_dir()}" }
342
- log.info(x) { "Gem User Home => #{Gem.user_home()}" }
343
-
344
- return
345
-
346
- end
347
-
348
-
349
- def unopened_envelope?( key_database )
350
-
351
- return false if key_database.has_key?( ENV_PATH )
352
- print_unopened_envelope()
353
- return true
354
-
355
- end
356
-
357
-
358
- def print_unopened_envelope()
359
-
360
- puts ""
361
- puts "Problem - before creating, reading or changing data you"
362
- puts "must first open a path to it like this."
363
- puts ""
364
- puts " #{COMMANDMENT} open email.accounts joe@gmail.com"
365
- puts ""
366
- puts " then you put data at that path"
367
- puts ""
368
- puts " #{COMMANDMENT} put username joebloggs"
369
- puts " #{COMMANDMENT} put password jo3s-s3cr3t"
370
- puts " #{COMMANDMENT} put phone-no 07123456789"
371
- puts " #{COMMANDMENT} put question \"Mums maiden name\""
372
- puts ""
373
- puts " and why not read it back"
374
- puts ""
375
- puts " #{COMMANDMENT} get password"
376
- puts ""
377
- puts " then close the path."
378
- puts ""
379
- puts " #{COMMANDMENT} close"
380
- puts ""
381
-
382
- end
383
-
384
-
385
- def print_already_logged_in
386
-
387
- puts ""
388
- puts "We are already logged in. Open a secret envelope, put, then seal."
389
- puts ""
390
- puts " #{COMMANDMENT} open aws.credentials:s3reader"
391
- puts " #{COMMANDMENT} put access_key ABCD1234"
392
- puts " #{COMMANDMENT} put secret_key FGHIJ56789"
393
- puts " #{COMMANDMENT} put region_key eu-central-1"
394
- puts " #{COMMANDMENT} seal"
395
- puts ""
396
-
397
- end
398
-
399
-
400
- def print_already_initialized
401
-
402
- puts ""
403
- puts "You can go ahead and login."
404
- puts "Your domain [#{@domain_name}] is already setup."
405
- puts "You should already know the password."
406
- puts ""
407
- puts " #{COMMANDMENT} login #{@domain_name}"
408
- puts ""
409
-
410
- end
411
-
412
-
413
- def print_domain_initialized
414
-
415
- puts ""
416
- puts "It is time to login."
417
- puts "The protector keys for [#{@domain_name}] have been setup."
418
- puts "From now on you simply login to use this domain."
419
- puts ""
420
- puts " #{COMMANDMENT} login #{@domain_name}"
421
- puts ""
422
-
423
- end
424
-
425
-
426
- def print_not_initialized
427
-
428
- puts ""
429
- puts "Please initialize the app domain on this machine."
430
- puts "Give a domain name and a folder for key storage."
431
- puts ""
432
- puts " #{COMMANDMENT} init <domain_name> \"$HOME/open.world\""
433
- puts ""
434
-
435
- end
436
-
437
-
438
- def print_success_initializing
439
-
440
- puts ""
441
- puts "Success - now open a secret envelope, put, then seal."
442
- puts ""
443
- puts " #{COMMANDMENT} open aws.credentials:s3reader"
444
- puts " #{COMMANDMENT} put access_key ABCD1234"
445
- puts " #{COMMANDMENT} put secret_key FGHIJ56789"
446
- puts " #{COMMANDMENT} put region_key eu-central-1"
447
- puts " #{COMMANDMENT} seal"
448
- puts ""
449
-
450
- end
451
-
452
-
453
- def print_login_success
454
-
455
- puts ""
456
- puts "Success - you are logged in."
457
- puts ""
458
- puts " #{COMMANDMENT} open aws.credentials:s3reader"
459
- puts " #{COMMANDMENT} put access_key ABCD1234"
460
- puts " #{COMMANDMENT} put secret_key FGHIJ56789"
461
- puts " #{COMMANDMENT} put region_key eu-central-1"
462
- puts " #{COMMANDMENT} seal"
463
- puts ""
464
-
465
- end
466
-
467
-
468
- end
469
-
470
-
471
- end