autodrop 0.1.0 → 1.0.0

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