ukemi 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a77579f014e97cc048e95dc5488e228ee19baba62d5074dbc9bfcb5c75f9d568
4
- data.tar.gz: e156b9c6de521ea49d7a69725c724176a5d1739deee7bba32aef4e662c64ddae
3
+ metadata.gz: ad938a7cb405569da9b6ba12dfe4c1bfa7eb382a4e08d2b204a8a6705572c1b9
4
+ data.tar.gz: d9b4a43bc7837c92b8fa692f7ecd12ba0f6de5aeda82f5066ea868b8b01b73e3
5
5
  SHA512:
6
- metadata.gz: f4e3eb6f8f4dd2223ba2eed1846ff8d95ee8d14913f5f6e682269489ee9348fea3af858c9bf849bac4d612a42a0a3c1c7bae3b27dde3461b1104c3e0e3752964
7
- data.tar.gz: 34467c49986dc11f2ac4fae646c1d96419ebe199838fe20a8f859c1619137ce2f3d0bcfc11e1f356545e14f63a6b5ff81b7ad9f39b206b70469959a8ca9a5986
6
+ metadata.gz: 31c864b566fe92d6ca5b71691c3517d95e29922ba704a7cd483c4df96a6731a7b42523d8102ea76b946ec1bc5068684475b4f60648064a2f466a41e70f014952
7
+ data.tar.gz: 561d1bd6ed05ec3392c2137a1b73df0a95ad1e20e0ccc66f354ce1a7725603fa7a50572bcbecb96edf4f932f9717b8520a35cd0879ff7e801d60e4dcc16e5486
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # ukemi
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/ukemi.svg)](https://badge.fury.io/rb/ukemi)
3
4
  [![Build Status](https://travis-ci.com/ninoseki/ukemi.svg?branch=master)](https://travis-ci.com/ninoseki/ukemi)
4
5
  [![Coverage Status](https://coveralls.io/repos/github/ninoseki/ukemi/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/ukemi?branch=master)
5
6
  [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/ukemi/badge)](https://www.codefactor.io/repository/github/ninoseki/ukemi)
@@ -15,7 +16,7 @@ It supports the following services.
15
16
 
16
17
  It outputs passive DNS resolutions as JSON.
17
18
 
18
- ## Instalattion
19
+ ## Installation
19
20
 
20
21
  ```bash
21
22
  gem install ukemi
@@ -46,375 +47,138 @@ $ ukemi help looup
46
47
  Usage:
47
48
  ukemi lookup [IP|DOMAIN]
48
49
 
49
- Lookup passive DNS services
50
+ Options:
51
+ [--order-by=ORDER_BY] # Ordering of the passve DNS resolutions (last_seen or first_seen)
52
+ # Default: -last_seen
53
+
54
+ Lookup passive DNS servicess
50
55
  ```
51
56
 
52
57
  ```bash
53
- $ ukemi lookup circl.lu
58
+ $ ukemi lookup example.com
54
59
  {
55
- "149.13.33.14": [
56
- {
57
- "firtst_seen": "2016-10-07",
58
- "last_seen": "2018-10-26",
59
- "source": "CIRCL"
60
- },
61
- {
62
- "firtst_seen": "2017-05-26",
63
- "last_seen": "2020-03-15",
64
- "source": "SecurityTrails"
65
- },
66
- {
67
- "firtst_seen": "2019-12-04",
68
- "last_seen": "2019-12-04",
69
- "source": "VirusTotal"
70
- }
71
- ],
72
- "149.13.33.4": [
73
- {
74
- "firtst_seen": "2011-03-08",
75
- "last_seen": "2012-02-13",
76
- "source": "CIRCL"
77
- },
78
- {
79
- "firtst_seen": "2013-07-30",
80
- "last_seen": "2013-07-30",
81
- "source": "VirusTotal"
82
- }
83
- ],
84
- "194.154.205.24": [
85
- {
86
- "firtst_seen": "2011-03-03",
87
- "last_seen": "2011-03-03",
88
- "source": "CIRCL"
89
- }
90
- ]
60
+ "93.184.216.34": {
61
+ "first_seen": "2016-03-01",
62
+ "last_seen": "2020-03-16",
63
+ "sources": [
64
+ {
65
+ "first_seen": "2016-10-07",
66
+ "last_seen": "2018-10-30",
67
+ "source": "CIRCL"
68
+ },
69
+ {
70
+ "first_seen": "2016-03-01",
71
+ "last_seen": "2020-03-16",
72
+ "source": "SecurityTrails"
73
+ },
74
+ {
75
+ "first_seen": "2020-03-03",
76
+ "last_seen": "2020-03-03",
77
+ "source": "VirusTotal"
78
+ }
79
+ ]
80
+ },
81
+ ...
91
82
  }
92
83
 
93
84
  $ ukemi lookup 195.123.226.243
94
85
  {
95
- "liankt.club": [
96
- {
97
- "firtst_seen": "2020-02-15",
98
- "last_seen": "2020-03-13",
99
- "source": "PassiveTotal"
100
- },
101
- {
102
- "firtst_seen": "2020-02-16",
103
- "last_seen": "2020-02-16",
104
- "source": "VirusTotal"
105
- }
106
- ],
107
- "weidt.club": [
108
- {
109
- "firtst_seen": "2020-03-12",
110
- "last_seen": "2020-03-12",
111
- "source": "PassiveTotal"
112
- }
113
- ],
114
- "jikt.club": [
115
- {
116
- "firtst_seen": "2020-03-04",
117
- "last_seen": "2020-03-12",
118
- "source": "PassiveTotal"
119
- },
120
- {
121
- "firtst_seen": "2020-03-05",
122
- "last_seen": "2020-03-05",
123
- "source": "VirusTotal"
124
- }
125
- ],
126
- "biesi.club": [
127
- {
128
- "firtst_seen": "2020-02-15",
129
- "last_seen": "2020-03-12",
130
- "source": "PassiveTotal"
131
- },
132
- {
133
- "firtst_seen": "2020-02-20",
134
- "last_seen": "2020-02-20",
135
- "source": "VirusTotal"
136
- }
137
- ],
138
- "kaikt.club": [
139
- {
140
- "firtst_seen": "2020-02-15",
141
- "last_seen": "2020-03-12",
142
- "source": "PassiveTotal"
143
- },
144
- {
145
- "firtst_seen": "2020-02-21",
146
- "last_seen": "2020-02-21",
147
- "source": "VirusTotal"
148
- }
149
- ],
150
- "zhaokt.club": [
151
- {
152
- "firtst_seen": "2020-02-15",
153
- "last_seen": "2020-03-11",
154
- "source": "PassiveTotal"
155
- },
156
- {
157
- "firtst_seen": "2020-02-18",
158
- "last_seen": "2020-02-18",
159
- "source": "VirusTotal"
160
- }
161
- ],
162
- "yangdt.club": [
163
- {
164
- "firtst_seen": "2020-02-26",
165
- "last_seen": "2020-03-10",
166
- "source": "PassiveTotal"
167
- },
168
- {
169
- "firtst_seen": "2020-02-27",
170
- "last_seen": "2020-02-27",
171
- "source": "VirusTotal"
172
- }
173
- ],
174
- "jinkt.club": [
175
- {
176
- "firtst_seen": "2020-02-21",
177
- "last_seen": "2020-03-10",
178
- "source": "PassiveTotal"
179
- },
180
- {
181
- "firtst_seen": "2020-02-22",
182
- "last_seen": "2020-02-22",
183
- "source": "VirusTotal"
184
- }
185
- ],
186
- "taokt.club": [
187
- {
188
- "firtst_seen": "2020-03-10",
189
- "last_seen": "2020-03-10",
190
- "source": "PassiveTotal"
191
- }
192
- ],
193
- "xinkt.club": [
194
- {
195
- "firtst_seen": "2020-02-17",
196
- "last_seen": "2020-03-09",
197
- "source": "PassiveTotal"
198
- },
199
- {
200
- "firtst_seen": "2020-02-19",
201
- "last_seen": "2020-02-19",
202
- "source": "VirusTotal"
203
- }
204
- ],
205
- "mail.realty-advertising.ru": [
206
- {
207
- "firtst_seen": "2019-11-08",
208
- "last_seen": "2020-03-09",
209
- "source": "PassiveTotal"
210
- }
211
- ],
212
- "realty-advertising.ru": [
213
- {
214
- "firtst_seen": "2019-11-08",
215
- "last_seen": "2020-03-06",
216
- "source": "PassiveTotal"
217
- }
218
- ],
219
- "ns1.realty-advertising.ru": [
220
- {
221
- "firtst_seen": "2019-12-02",
222
- "last_seen": "2020-03-04",
223
- "source": "PassiveTotal"
224
- }
225
- ],
226
- "ns2.realty-advertising.ru": [
227
- {
228
- "firtst_seen": "2019-12-04",
229
- "last_seen": "2020-03-04",
230
- "source": "PassiveTotal"
231
- }
232
- ],
233
- "xiankt.club": [
234
- {
235
- "firtst_seen": "2020-02-15",
236
- "last_seen": "2020-03-03",
237
- "source": "PassiveTotal"
238
- },
239
- {
240
- "firtst_seen": "2020-02-16",
241
- "last_seen": "2020-02-16",
242
- "source": "VirusTotal"
243
- }
244
- ],
245
- "nittsu-si.com": [
246
- {
247
- "firtst_seen": "2020-02-15",
248
- "last_seen": "2020-03-03",
249
- "source": "PassiveTotal"
250
- },
251
- {
252
- "firtst_seen": "2020-02-21",
253
- "last_seen": "2020-02-21",
254
- "source": "VirusTotal"
255
- }
256
- ],
257
- "mailer.realty-advertising.ru": [
258
- {
259
- "firtst_seen": "2020-02-23",
260
- "last_seen": "2020-02-23",
261
- "source": "PassiveTotal"
262
- }
263
- ],
264
- "mail7.realty-advertising.ru": [
265
- {
266
- "firtst_seen": "2020-02-23",
267
- "last_seen": "2020-02-23",
268
- "source": "PassiveTotal"
269
- }
270
- ],
271
- "zimbra.realty-advertising.ru": [
272
- {
273
- "firtst_seen": "2020-02-23",
274
- "last_seen": "2020-02-23",
275
- "source": "PassiveTotal"
276
- }
277
- ],
278
- "relay2.realty-advertising.ru": [
279
- {
280
- "firtst_seen": "2020-02-23",
281
- "last_seen": "2020-02-23",
282
- "source": "PassiveTotal"
283
- }
284
- ],
285
- "sniper.realty-advertising.ru": [
286
- {
287
- "firtst_seen": "2020-02-22",
288
- "last_seen": "2020-02-22",
289
- "source": "PassiveTotal"
290
- }
291
- ],
292
- "mailx.realty-advertising.ru": [
293
- {
294
- "firtst_seen": "2020-02-22",
295
- "last_seen": "2020-02-22",
296
- "source": "PassiveTotal"
297
- }
298
- ],
299
- "send.realty-advertising.ru": [
300
- {
301
- "firtst_seen": "2020-02-22",
302
- "last_seen": "2020-02-22",
303
- "source": "PassiveTotal"
304
- }
305
- ],
306
- "mta.realty-advertising.ru": [
307
- {
308
- "firtst_seen": "2020-02-22",
309
- "last_seen": "2020-02-22",
310
- "source": "PassiveTotal"
311
- }
312
- ],
313
- "home.realty-advertising.ru": [
314
- {
315
- "firtst_seen": "2020-02-22",
316
- "last_seen": "2020-02-22",
317
- "source": "PassiveTotal"
318
- }
319
- ],
320
- "pbrand.realty-advertising.ru": [
321
- {
322
- "firtst_seen": "2020-02-22",
323
- "last_seen": "2020-02-22",
324
- "source": "PassiveTotal"
325
- }
326
- ],
327
- "smtpauth.realty-advertising.ru": [
328
- {
329
- "firtst_seen": "2020-02-22",
330
- "last_seen": "2020-02-22",
331
- "source": "PassiveTotal"
332
- }
333
- ],
334
- "gate.realty-advertising.ru": [
335
- {
336
- "firtst_seen": "2020-02-21",
337
- "last_seen": "2020-02-21",
338
- "source": "PassiveTotal"
339
- }
340
- ],
341
- "mx02.realty-advertising.ru": [
342
- {
343
- "firtst_seen": "2020-02-21",
344
- "last_seen": "2020-02-21",
345
- "source": "PassiveTotal"
346
- }
347
- ],
348
- "outmail.realty-advertising.ru": [
349
- {
350
- "firtst_seen": "2020-02-21",
351
- "last_seen": "2020-02-21",
352
- "source": "PassiveTotal"
353
- }
354
- ],
355
- "exchange.realty-advertising.ru": [
356
- {
357
- "firtst_seen": "2020-02-21",
358
- "last_seen": "2020-02-21",
359
- "source": "PassiveTotal"
360
- }
361
- ],
362
- "ms.realty-advertising.ru": [
363
- {
364
- "firtst_seen": "2020-02-21",
365
- "last_seen": "2020-02-21",
366
- "source": "PassiveTotal"
367
- }
368
- ],
369
- "owa.realty-advertising.ru": [
370
- {
371
- "firtst_seen": "2020-02-20",
372
- "last_seen": "2020-02-20",
373
- "source": "PassiveTotal"
374
- }
375
- ],
376
- "mail8.realty-advertising.ru": [
377
- {
378
- "firtst_seen": "2020-02-20",
379
- "last_seen": "2020-02-20",
380
- "source": "PassiveTotal"
381
- }
382
- ],
383
- "mta-sts.realty-advertising.ru": [
384
- {
385
- "firtst_seen": "2019-11-11",
386
- "last_seen": "2020-02-08",
387
- "source": "PassiveTotal"
388
- }
389
- ],
390
- "mail02.realty-advertising.ru": [
391
- {
392
- "firtst_seen": "2020-01-18",
393
- "last_seen": "2020-01-18",
394
- "source": "PassiveTotal"
395
- }
396
- ],
397
- "www.realty-advertising.ru": [
398
- {
399
- "firtst_seen": "2019-11-08",
400
- "last_seen": "2019-11-12",
401
- "source": "PassiveTotal"
402
- }
403
- ],
404
- "ln-048.rd-00003024.id-11744955.v0.tun.vpnoverdns.com": [
405
- {
406
- "firtst_seen": "2017-04-06",
407
- "last_seen": "2017-04-06",
408
- "source": "PassiveTotal"
409
- }
410
- ],
411
- "mnen6k7g.info": [
412
- {
413
- "firtst_seen": "2010-10-28",
414
- "last_seen": "2010-10-28",
415
- "source": "PassiveTotal"
416
- }
417
- ]
86
+ "example.org": {
87
+ "first_seen": "2011-04-11",
88
+ "last_seen": "2020-03-16",
89
+ "sources": [
90
+ {
91
+ "first_seen": "2011-04-11",
92
+ "last_seen": "2011-04-11",
93
+ "source": "CIRCL"
94
+ },
95
+ {
96
+ "first_seen": "2016-10-09",
97
+ "last_seen": "2018-10-28",
98
+ "source": "CIRCL"
99
+ },
100
+ {
101
+ "first_seen": "2014-12-09",
102
+ "last_seen": "2020-03-16",
103
+ "source": "PassiveTotal"
104
+ },
105
+ {
106
+ "first_seen": null,
107
+ "last_seen": null,
108
+ "source": "SecurityTrails"
109
+ }
110
+ ]
111
+ },
112
+ ...
113
+ }
114
+
115
+ # You can specify the order of resolutions
116
+
117
+ # Order by last_seen DESC
118
+ $ ukemi lookup example.com --order-by -last_seen
119
+
120
+ # Order by last_seen ASC
121
+ $ ukemi lookup example.com --order-by last_seen
122
+
123
+ # Order by first_seen DESC
124
+ $ ukemi lookup example.com --order-by -first_seen
125
+
126
+ # Order by first_seen ASC
127
+ $ ukemi lookup example.com --order-by first_seen
128
+ ```
129
+
130
+ ### Using with jq
131
+
132
+ [jq](https://stedolan.github.io/jq/)'s powerful processor helps to interact with the output.
133
+
134
+ ```bash
135
+ # List up resolutions only
136
+ $ ukemi lookup example.com | jq "keys"
137
+ [
138
+ "192.0.32.10",
139
+ "192.0.43.10",
140
+ "208.77.188.166",
141
+ "209.67.208.202",
142
+ "221.121.159.162",
143
+ "93.184.216.119",
144
+ "93.184.216.34"
145
+ ]
146
+
147
+ # List up the first 2 objects
148
+ $ ukemi lookup example.com | jq "to_entries | .[:2] | from_entries"
149
+ {
150
+ "93.184.216.34": {
151
+ "first_seen": "2016-03-01",
152
+ "last_seen": "2020-03-16",
153
+ "sources": [
154
+ {
155
+ "first_seen": "2016-10-07",
156
+ "last_seen": "2018-10-30",
157
+ "source": "CIRCL"
158
+ },
159
+ {
160
+ "first_seen": "2016-03-01",
161
+ "last_seen": "2020-03-16",
162
+ "source": "SecurityTrails"
163
+ },
164
+ {
165
+ "first_seen": "2020-03-03",
166
+ "last_seen": "2020-03-03",
167
+ "source": "VirusTotal"
168
+ }
169
+ ]
170
+ },
171
+ "221.121.159.162": {
172
+ "first_seen": "2019-11-04",
173
+ "last_seen": "2019-11-04",
174
+ "sources": [
175
+ {
176
+ "first_seen": "2019-11-04",
177
+ "last_seen": "2019-11-04",
178
+ "source": "VirusTotal"
179
+ }
180
+ ]
181
+ }
418
182
  }
419
183
  ```
420
184
 
@@ -28,4 +28,6 @@ require "ukemi/services/virustotal"
28
28
 
29
29
  require "ukemi/moderator"
30
30
 
31
+ require "ukemi/configuration"
32
+
31
33
  require "ukemi/cli"
@@ -6,8 +6,11 @@ require "thor"
6
6
  module Ukemi
7
7
  class CLI < Thor
8
8
  desc "lookup [IP|DOMAIN]", "Lookup passive DNS services"
9
+ method_option :order_by, type: :string, desc: "Ordering of the passve DNS resolutions (last_seen or first_seen)", default: "-last_seen"
9
10
  def lookup(data)
10
11
  data = refang(data)
12
+ set_ordering options["order_by"]
13
+
11
14
  result = Moderator.lookup(data)
12
15
  puts JSON.pretty_generate(result)
13
16
  end
@@ -16,6 +19,17 @@ module Ukemi
16
19
  def refang(data)
17
20
  data.gsub("[.]", ".").gsub("(.)", ".")
18
21
  end
22
+
23
+ def set_ordering(order_by)
24
+ parts = order_by.split("-")
25
+ ordering_key = parts.last
26
+ sort_order = parts.length == 2 ? "DESC" : "ASC"
27
+
28
+ Ukemi.configure do |config|
29
+ config.ordering_key = ordering_key
30
+ config.sort_order = sort_order
31
+ end
32
+ end
19
33
  end
20
34
  end
21
35
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukemi
4
+ class Configuration
5
+ attr_accessor :ordering_key
6
+ attr_accessor :sort_order
7
+
8
+ def initialize
9
+ @ordering_key = "last_seen"
10
+ @sort_order = "DESC"
11
+ end
12
+ end
13
+
14
+ class << self
15
+ def configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ attr_writer :configuration
20
+
21
+ def configure
22
+ yield configuration
23
+ end
24
+ end
25
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "parallel"
4
4
  require "time"
5
+ require "date"
5
6
 
6
7
  module Ukemi
7
8
  class Moderator
@@ -17,29 +18,58 @@ module Ukemi
17
18
  end
18
19
  end.flatten.compact
19
20
 
21
+ format records
22
+ end
23
+
24
+ def format(records)
20
25
  memo = Hash.new { |h, k| h[k] = [] }
26
+
21
27
  records.each do |record|
22
28
  memo[record.data] << {
23
- firtst_seen: record.first_seen,
29
+ first_seen: record.first_seen,
24
30
  last_seen: record.last_seen,
25
31
  source: record.source,
26
32
  }
27
33
  end
34
+ # Merge first seen last seen and make the sources a list.
35
+ formatted = memo.map do |key, sources|
36
+ first_seens = sources.map { |record| convert_to_unixtime record.dig(:first_seen) }.compact
37
+ last_seens = sources.map { |record| convert_to_unixtime record.dig(:last_seen) }.compact
38
+ [
39
+ key,
40
+ {
41
+ first_seen: convert_to_date(first_seens.min),
42
+ last_seen: convert_to_date(last_seens.max),
43
+ sources: sources
44
+ }
45
+ ]
46
+ end.to_h
28
47
 
29
- memo.sort_by do |_key, array|
30
- last_seens = array.map do |record|
31
- parse_to_unixtime record.dig(:last_seen)
48
+ # Sorting
49
+ ordering_key = Ukemi.configuration.ordering_key.to_sym
50
+ sort_order = Ukemi.configuration.sort_order
51
+ formatted.sort_by do |_key, hash|
52
+ value = hash.dig(ordering_key)
53
+ if sort_order == "DESC"
54
+ value ? -convert_to_unixtime(value) : -1
55
+ else
56
+ value ? convert_to_unixtime(value) : Float::MAX.to_i
32
57
  end
33
- -last_seens.max
34
58
  end.to_h
35
59
  end
36
60
 
37
- def parse_to_unixtime(date)
38
- return -1 unless date
61
+ def convert_to_unixtime(date)
62
+ return nil unless date
39
63
 
40
64
  Time.parse(date).to_i
41
65
  end
42
66
 
67
+ def convert_to_date(time)
68
+ return nil unless time
69
+
70
+ Time.at(time).to_date.to_s
71
+ end
72
+
43
73
  class << self
44
74
  def lookup(data)
45
75
  new.lookup data
@@ -33,23 +33,24 @@ module Ukemi
33
33
  def lookup_by_domain(data)
34
34
  result = api.history.get_all_dns_history(data, type: "a")
35
35
  records = result.dig("records") || []
36
- records.map do |record|
36
+
37
+ memo = Hash.new { |h, k| h[k] = [] }
38
+ records.each do |record|
37
39
  values = record.dig("values") || []
38
- values.map do |value|
39
- Record.new(
40
- data: value.dig("ip"),
41
- first_seen: record.dig("first_seen"),
42
- last_seen: record.dig("last_seen"),
43
- source: name
44
- )
40
+ values.each do |value|
41
+ ip = value.dig("ip")
42
+ memo[ip] << record.dig("first_seen")
43
+ memo[ip] << record.dig("last_seen")
45
44
  end
46
- end.flatten
47
- end
45
+ end
48
46
 
49
- def extract_attributes(response)
50
- data = response.dig("data") || []
51
- data.map do |item|
52
- item.dig("attributes") || []
47
+ memo.keys.map do |ip|
48
+ Record.new(
49
+ data: ip,
50
+ first_seen: memo[ip].min,
51
+ last_seen: memo[ip].max,
52
+ source: name
53
+ )
53
54
  end
54
55
  end
55
56
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ukemi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ukemi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manabu Niseki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-15 00:00:00.000000000 Z
11
+ date: 2020-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -240,6 +240,7 @@ files:
240
240
  - exe/ukemi
241
241
  - lib/ukemi.rb
242
242
  - lib/ukemi/cli.rb
243
+ - lib/ukemi/configuration.rb
243
244
  - lib/ukemi/error.rb
244
245
  - lib/ukemi/moderator.rb
245
246
  - lib/ukemi/record.rb