autodrop 0.1.0 → 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04e54913693a2775c30c1b7fa6e234553f8b4ead
4
+ data.tar.gz: bd7c79f00d1dba1c50be7ce0530d937acd303be0
5
+ SHA512:
6
+ metadata.gz: 38f7d6bafa9419be5c0d214aa7737074454e1e4019f3549d6982d5ff153ac8f5a4dce6a84cb875e968350192c742d1ba6c56910c8343e598630e7d17008a3180
7
+ data.tar.gz: 146af191bda48a1c4e2b654623ba44ce4390d80b109072e77f24882f4d2362654be92c4b60e98ba2a136877fd3ca8f0c93bea9924580ab71536c32ab75b55b2e
data/ChangeLog CHANGED
@@ -0,0 +1,13 @@
1
+ 2017-03-21 noz
2
+
3
+ * heavily refactored
4
+
5
+ Thu May 14 20:31:36 2009 noz <noz@delta>
6
+
7
+ * .gitignore: add 'pkg'
8
+ * bin/.gitignore: added
9
+
10
+ Wed May 13 06:32:32 2009 noz <noz@delta>
11
+
12
+ * Rakefile: disable gem tasks when rubygems is not present
13
+
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2009, NOZAWA Hiromasa. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in
11
+ the documentation and/or other materials provided with the
12
+ distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile CHANGED
@@ -1,75 +1,40 @@
1
- # -*-ruby-*-
1
+ require "rake/clean"
2
+ require "rubygems/package_task"
2
3
 
3
- FILES = [
4
- 'ChangeLog',
5
- 'Rakefile',
6
- 'bin/autodrop',
7
- 'bin/autodrop.rb',
8
- 'conf/autodrop.conf.default',
9
- 'doc/README.jp.txt',
10
- 'doc/README.txt',
11
- 'misc/autodrop.sh',
12
- ]
4
+ task :default => :gem
13
5
 
14
- desc "Same as 'rake bin/autodrop'"
15
- task :default => [ "bin/autodrop" ]
16
-
17
- desc "Make bin/autodrop"
18
- file "bin/autodrop" do
19
- sh "cp -f bin/autodrop.rb bin/autodrop"
20
- sh "chmod 755 bin/autodrop"
21
- end
22
-
23
- desc "Do distclean"
24
- task :distclean => [ :clobber_package ] do
25
- sh "rm -f bin/autodrop"
6
+ file "autodrop" do
7
+ sh "cp -f autodrop.rb autodrop"
8
+ sh "chmod 755 autodrop"
26
9
  end
27
10
 
28
- task :gem => [ "bin/autodrop" ]
29
- require 'rubygems'
30
- require 'rake/gempackagetask'
31
- spec = Gem::Specification.new do |s|
11
+ task :gem => [ "autodrop" ]
12
+ spec = Gem::Specification.new { |s|
32
13
  s.name = "autodrop"
33
- s.version = "0.1.0"
14
+ s.version = "1.0.0"
34
15
  s.author = "NOZAWA Hiromasa"
35
16
  s.summary = "Automatic iptables DROP daemon"
36
- s.homepage = 'http://rubyforge.org/projects/autodrop'
37
- s.rubyforge_project = 'autodrop'
38
- s.files = FILES
39
- s.bindir = 'bin'
40
- s.executables = 'autodrop'
41
- s.require_path = []
42
- end
43
- Rake::GemPackageTask.new(spec) do |pkg|
17
+ s.license = "BSD-2-Clause"
18
+ s.homepage = "https://github.com/noz/autodrop"
19
+ s.add_runtime_dependency "trad-getopt"
20
+ s.files = FileList[
21
+ "ChangeLog",
22
+ "LICENSE",
23
+ "Rakefile",
24
+ "autodrop",
25
+ "autodrop.conf.default",
26
+ "autodrop.rb",
27
+ "autodrop.sh",
28
+ "autodrop.txt",
29
+ ]
30
+ s.bindir = "."
31
+ s.executables = "autodrop"
32
+ s.require_path = "."
33
+ }
34
+ Gem::PackageTask.new(spec) {|pkg|
35
+ pkg.need_tar_gz = true
36
+ pkg.need_tar_bz2 = true
44
37
  pkg.need_zip = true
45
- pkg.need_tar = true
46
- end
38
+ }
47
39
 
48
- #
49
- # for manual installation (without rubygem)
50
- #
51
-
52
- DEFAULT_BIN_INSTALL_DIR = '/usr/local/sbin'
53
- DEFAULT_CONF_INSTALL_DIR = '/etc'
54
-
55
- desc "Install (without rubygem)"
56
- task :install => "bin/autodrop" do
57
- bin_install_dir = ENV['BIN_INSTALL_DIR']
58
- bin_install_dir ||= DEFAULT_BIN_INSTALL_DIR
59
- conf_install_dir = ENV['CONF_INSTALL_DIR']
60
- conf_install_dir ||= DEFAULT_CONF_INSTALL_DIR
61
- directory bin_install_dir
62
- directory conf_install_dir
63
- sh "cp -f bin/autodrop #{bin_install_dir}/autodrop"
64
- sh "cp -f conf/autodrop.conf.default #{conf_install_dir}/autodrop.conf.default"
65
- end
66
-
67
- desc "Uninstall (without rubygem)"
68
- task :uninstall do
69
- bin_install_dir = ENV['BIN_INSTALL_DIR']
70
- bin_install_dir ||= DEFAULT_BIN_INSTALL_DIR
71
- conf_install_dir = ENV['CONF_INSTALL_DIR']
72
- conf_install_dir ||= DEFAULT_CONF_INSTALL_DIR
73
- sh "rm -f #{bin_install_dir}/autodrop"
74
- sh "rm -f #{conf_install_dir}/autodrop.conf.default"
75
- end
40
+ CLOBBER << [ "autodrop" ]
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env ruby
2
+ require "syslog"
3
+ require "trad-getopt"
4
+ require "yaml"
5
+
6
+ class Autodrop
7
+
8
+ VERSION = "1.0.0"
9
+
10
+ class SyslogLogger
11
+ def initialize
12
+ Syslog.open "autodrop", Syslog::LOG_PID|Syslog::LOG_CONS, Syslog::LOG_AUTH
13
+ end
14
+ def log msg
15
+ Syslog.info msg
16
+ end
17
+ end
18
+
19
+ class StdoutLogger
20
+ def log msg
21
+ puts "#{Time.now}: #{msg}"
22
+ end
23
+ end
24
+
25
+ class FileLogger
26
+ def initialize file
27
+ @file = open file, "a+"
28
+ end
29
+ def log msg
30
+ @file.puts "#{Time.now}: #{msg}"
31
+ @file.flush
32
+ end
33
+ end
34
+
35
+ CONF_KEYS = [
36
+ KW_COUNT = "count",
37
+ KW_DAEMON = "daemon",
38
+ KW_DROP_COMMAND = "drop-command",
39
+ KW_DURATION = "duration",
40
+ KW_INPUT = "input",
41
+ KW_LOG = "log",
42
+ KW_PATTERNS = "patterns",
43
+ KW_PIDFILE = "pidfile",
44
+ KW_VERBOSE = "verbose",
45
+ ]
46
+
47
+ CONF_DEFAULTS = {
48
+ KW_COUNT => 3,
49
+ KW_DAEMON => true,
50
+ KW_DROP_COMMAND => [
51
+ "/sbin/iptables", "-I", "INPUT", "-s", "%remote_address%", "-j", "DROP" ],
52
+ KW_DURATION => 10,
53
+ KW_INPUT => "/var/log/authfifo",
54
+ KW_LOG => "syslog",
55
+ KW_PATTERNS => [],
56
+ KW_PIDFILE => "/var/run/autodrop.pid",
57
+ KW_VERBOSE => false,
58
+ }
59
+
60
+ def self.loadconf file
61
+ conf = YAML.load File.read file
62
+ raise "wrong format" unless conf.is_a? Hash
63
+ conf = CONF_DEFAULTS.merge conf
64
+
65
+ badkw = conf.keys - CONF_KEYS
66
+ raise "unknown keywords - #{badkw.join ","}" unless badkw.empty?
67
+
68
+ [ KW_COUNT, KW_DURATION ].each { |k|
69
+ unless conf[k].is_a? Integer
70
+ raise "integer required - #{k}: #{conf[k].inspect}"
71
+ end
72
+ }
73
+ [ KW_DROP_COMMAND, KW_INPUT, KW_LOG, KW_PIDFILE ].each { |k|
74
+ unless conf[k].is_a? String
75
+ raise "string required - #{k}: #{conf[k].inspect}"
76
+ end
77
+ }
78
+ conf[KW_DROP_COMMAND] = conf[KW_DROP_COMMAND].split
79
+
80
+ conf[KW_PATTERNS].map! { |item|
81
+ unless [ String, Regexp ].include? item.class
82
+ raise "string or regexp required - #{item.inspect}"
83
+ end
84
+ Regexp.new item
85
+ }
86
+
87
+ conf
88
+ end
89
+
90
+ def terminate whymsg, status = 0
91
+ @logger.log "terminate (#{whymsg})"
92
+ exit status
93
+ end
94
+ private :terminate
95
+
96
+ class Dog
97
+ def initialize addr, config
98
+ @addr = addr
99
+ @duration = config[KW_DURATION]
100
+ @expire = Time.now + config[KW_DURATION]
101
+ @count_max = config[KW_COUNT]
102
+ @count = 1
103
+ @drop_command = config[KW_DROP_COMMAND].map { |e|
104
+ e == "%remote_address%" ? addr : e
105
+ }
106
+ end
107
+ attr_reader :addr, :count_max, :drop_command, :duration
108
+ attr_accessor :count, :expire
109
+ end
110
+
111
+ def run config
112
+ @logger = nil
113
+ case config[KW_LOG]
114
+ when "syslog"
115
+ @logger = SyslogLogger.new
116
+ when "stdout"
117
+ @logger = StdoutLogger.new
118
+ else
119
+ begin
120
+ @logger = FileLogger.new config[KW_LOG]
121
+ rescue
122
+ warn "autodrop: can not open - #{config[KW_LOG]}"
123
+ exit 1
124
+ end
125
+ end
126
+
127
+ if config[KW_DAEMON]
128
+ if Process.respond_to? :daemon
129
+ Process.daemon
130
+ else
131
+ exit if fork
132
+ Process.setsid
133
+ exit if fork
134
+ Dir.chdir "/"
135
+ STDIN.reopen "/dev/null"
136
+ STDOUT.reopen "/dev/null"
137
+ STDERR.reopen "/dev/null"
138
+ end
139
+ File.write config[KW_PIDFILE], Process.pid
140
+ end
141
+
142
+ trap("SIGINT") { terminate "SIGINT" }
143
+ trap("SIGTERM") { terminate "SIGTERM" }
144
+ trap("SIGHUP") { terminate "SIGHUP" }
145
+
146
+ @logger.log "start watching - #{config[KW_INPUT]}"
147
+ unless File.ftype(config[KW_INPUT]) == "fifo"
148
+ raise "not a named pipe - #{config[KW_INPUT]}"
149
+ end
150
+ fifofp = File.open config[KW_INPUT], File::RDWR
151
+
152
+ @dogs = {}
153
+ loop {
154
+ ready = select [fifofp]
155
+ next unless ready
156
+
157
+ addr = nil
158
+ line = ready[0][0].gets
159
+ config[KW_PATTERNS].each { |pat|
160
+ if line =~ pat
161
+ addr = $1
162
+ break
163
+ end
164
+ }
165
+ next unless addr
166
+
167
+ dog = @dogs[addr]
168
+ if dog
169
+ dog.count += 1
170
+ dog.expire = Time.now + dog.duration
171
+ @logger.log "#{dog.addr} bark! (#{dog.count})" if config[KW_VERBOSE]
172
+ next
173
+ end
174
+
175
+ dog = Dog.new addr, config
176
+ Thread.new(dog) { |dog|
177
+ begin
178
+ @dogs[dog.addr] = dog
179
+
180
+ @logger.log "#{dog.addr} bark! (#{dog.count})" if config[KW_VERBOSE]
181
+
182
+ loop {
183
+ break if Time.now >= dog.expire
184
+
185
+ if dog.count >= dog.count_max
186
+ @logger.log "#{dog.addr} DROP"
187
+ out = IO.popen(dog.drop_command, "r+", :err => [ :child, :out ]) { |io|
188
+ buf = []
189
+ while l = io.gets
190
+ buf.push l.chomp
191
+ end
192
+ buf
193
+ }
194
+ st = $?.exitstatus
195
+ if st != 0
196
+ @logger.log "DROP fail. command exit status #{st}"
197
+ out.each { |l| @logger.log "|#{l}" }
198
+ end
199
+ break
200
+ end
201
+
202
+ # @logger.log "#{dog.addr} grrr" if config[KW_VERBOSE]
203
+ sleep 1
204
+ }
205
+ rescue => ex
206
+ @logger.log "error in worker"
207
+ @logger.log "|#{ex}"
208
+ ex.backtrace.each { |l| @logger.log "|#{l}" }
209
+ ensure
210
+ @dogs.delete dog.addr
211
+ @logger.log "#{dog.addr} leave" if config[KW_VERBOSE]
212
+ end
213
+ }
214
+ }
215
+ rescue => ex
216
+ @logger.log ex.message
217
+ ex.backtrace.each { |l| @logger.log "|#{l}" }
218
+ terminate "error", 1
219
+ ensure
220
+ File.unlink config[KW_PIDFILE] rescue nil
221
+ end
222
+ end
223
+
224
+ ### main
225
+
226
+ DEFAULT_CONFFILE = "/etc/autodrop.conf"
227
+
228
+ def usage
229
+ puts <<EOD
230
+ usage: autodrop [options]
231
+ -c, --config=FILE specify config file
232
+ -h, --help print this
233
+ -i, --input=FILE specify input source
234
+ -l, --log=FILE specify log file or `syslog'
235
+ -n, --no-daemon run as no daemon
236
+ -p, --pidfile=FILE specify PID file
237
+ -V, --version print version
238
+ EOD
239
+ end
240
+
241
+ conffile = nil
242
+ opts = "c:i:hl:np:V"
243
+ longopts = {
244
+ "config" => :required_argument,
245
+ "help" => :no_argument,
246
+ "input" => :required_argument,
247
+ "log" => :required_argument,
248
+ "no-daemon" => :no_argument,
249
+ "pidfile" => :required_argument,
250
+ "version" => :no_argument,
251
+ }
252
+
253
+ av = ARGV.dup
254
+ while (op, optarg = getopt(av, opts, longopts,
255
+ allow_empty_optarg:false, permute:true))
256
+ case op
257
+ when "c", "config"
258
+ if optarg[0] == "/"
259
+ conffile = optarg
260
+ else
261
+ conffile = File.join Dir.pwd, optarg
262
+ end
263
+ when "h", "help"
264
+ usage
265
+ exit
266
+ when "V", "version"
267
+ puts Autodrop::VERSION
268
+ exit
269
+ else
270
+ exit 1 if op.is_a? Symbol
271
+ end
272
+ end
273
+
274
+ conffile ||= DEFAULT_CONFFILE
275
+ begin
276
+ conf = Autodrop.loadconf conffile
277
+ rescue => ex
278
+ warn "autodrop: #{conffile} - #{ex}"
279
+ exit 1
280
+ end
281
+
282
+ while (op, optarg = getopt ARGV, opts, longopts, allow_empty_optarg:false)
283
+ case op
284
+ when "c", "config", "h", "help", "V", "version"
285
+ ;
286
+ when "i", "input"
287
+ if optarg[0] == "/"
288
+ conf[Autodrop::KW_INPUT] = optarg
289
+ else
290
+ conf[Autodrop::KW_INPUT] = File.join Dir.pwd, optarg
291
+ end
292
+ when "l", "log"
293
+ case optarg
294
+ when "syslog"
295
+ conf[Autodrop::KW_LOG] = "syslog"
296
+ else
297
+ if optarg[0] == "/"
298
+ conf[Autodrop::KW_LOG] = optarg
299
+ else
300
+ conf[Autodrop::KW_LOG] = File.join Dir.pwd, optarg
301
+ end
302
+ end
303
+ when "n", "no-daemon"
304
+ conf[Autodrop::KW_DAEMON] = false
305
+ conf[Autodrop::KW_LOG] = "stdout"
306
+ when "p", "pidfile"
307
+ if optarg[0] == "/"
308
+ conf[Autodrop::KW_PIDFILE] = optarg
309
+ else
310
+ conf[Autodrop::KW_PIDFILE] = File.join Dir.pwd, optarg
311
+ end
312
+ else
313
+ exit 1
314
+ end
315
+ end
316
+ unless ARGV.empty?
317
+ warn "autodrop: no arguments required - #{ARGV.join " "}"
318
+ exit 1
319
+ end
320
+
321
+ Autodrop.new.run conf