post_policy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +43 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/bin/postpolicy.rb +34 -0
- data/dbconfig.yml +6 -0
- data/lib/postpolicy.rb +15 -0
- data/lib/postpolicy/access_manager.rb +36 -0
- data/lib/postpolicy/config.rb +64 -0
- data/lib/postpolicy/extensions.rb +2 -0
- data/lib/postpolicy/extensions/hash.rb +7 -0
- data/lib/postpolicy/extensions/string.rb +11 -0
- data/lib/postpolicy/logger.rb +28 -0
- data/lib/postpolicy/plugins/acl/recipient.rb +13 -0
- data/lib/postpolicy/plugins/acl/sender.rb +13 -0
- data/lib/postpolicy/plugins/base/access.rb +20 -0
- data/lib/postpolicy/plugins/base/acl.rb +19 -0
- data/lib/postpolicy/plugins/base/datasource.rb +17 -0
- data/lib/postpolicy/plugins/datasource/file.rb +14 -0
- data/lib/postpolicy/plugins/datasource/regex.rb +17 -0
- data/lib/postpolicy/plugins/datasource/sql.rb +35 -0
- data/lib/postpolicy/plugins/datasource/value.rb +12 -0
- data/lib/postpolicy/protocol.rb +100 -0
- data/lib/postpolicy/rule.rb +67 -0
- data/lib/postpolicy/server.rb +24 -0
- data/lib/postpolicy/version.rb +9 -0
- data/post_policy.gemspec +85 -0
- data/postpolicy.yml +14 -0
- data/spec/access_manager_spec.rb +47 -0
- data/spec/acl_spec.rb +21 -0
- data/spec/config_spec.rb +26 -0
- data/spec/datasource_spec.rb +61 -0
- data/spec/format_spec.rb +35 -0
- data/spec/protocol_spec.rb +42 -0
- data/spec/rule_spec.rb +32 -0
- data/spec/test_helper.rb +27 -0
- metadata +99 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Michał Łomnicki
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= PostPolicy: Postfix Policy Server in Ruby
|
2
|
+
|
3
|
+
PostPolicy uses ACL system, which allow administrators to create rules based on mail source.
|
4
|
+
Unlike simple Postfix policy restrictions in PostPolicy one can create very complex rules against incoming mail.
|
5
|
+
PostPolicy is built on top of eventmachine, event-driven network library used for critical networked applications.
|
6
|
+
|
7
|
+
<b>PostPolicy is under heavy development so don't expect too much at the moment ;)</b>
|
8
|
+
|
9
|
+
== DEPENDENCIES
|
10
|
+
|
11
|
+
* eventmachine
|
12
|
+
* rpsec (only for tests)
|
13
|
+
|
14
|
+
== USAGE
|
15
|
+
|
16
|
+
Configure postpolicy in /etc/postpolicy.yml
|
17
|
+
|
18
|
+
Read http://www.postfix.org/SMTPD_POLICY_README.html
|
19
|
+
|
20
|
+
append to your master.cf
|
21
|
+
|
22
|
+
policy unix - n n - 0 spawn user=nobody argv=/path/to/postpolicy
|
23
|
+
|
24
|
+
in your main.cf
|
25
|
+
|
26
|
+
smtpd_recipient_restrictions =
|
27
|
+
...
|
28
|
+
reject_unauth_destination
|
29
|
+
check_policy_service unix:private/policy
|
30
|
+
|
31
|
+
== ABOUT
|
32
|
+
|
33
|
+
Author:: Michał Łomnicki <michal@lomnicki.com.pl>
|
34
|
+
License:: Copyright 2009 by Michał Łomnicki
|
35
|
+
Released under a MIT license.
|
36
|
+
|
37
|
+
== Warranty
|
38
|
+
|
39
|
+
This software is provided "as is" and without any express or
|
40
|
+
implied warranties, including, without limitation, the implied
|
41
|
+
warranties of merchantibility and fitness for a particular
|
42
|
+
purpose.
|
43
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "post_policy"
|
5
|
+
gemspec.summary = "Postfix Policy Server in Ruby"
|
6
|
+
gemspec.description = 'PostPolicy uses ACL system, which allow administrators to create rules based on mail source.'
|
7
|
+
gemspec.email = "michal@lomnicki.com.pl"
|
8
|
+
gemspec.homepage = "http://github.com/mlomnicki/post_policy"
|
9
|
+
gemspec.authors = ["Michał Łomnicki"]
|
10
|
+
end
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
|
14
|
+
end
|
15
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/postpolicy.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require File.join( File.dirname( __FILE__ ), '..', 'lib', 'postpolicy' )
|
5
|
+
|
6
|
+
DEFAULT_CONFIG = File.exists?( '/etc/postpolicy.yml' ) ? '/etc/postpolicy.yml' :
|
7
|
+
File.join( File.dirname( __FILE__ ), '../postpolicy.yml')
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
:verbose => false,
|
11
|
+
:config => DEFAULT_CONFIG
|
12
|
+
}
|
13
|
+
|
14
|
+
begin
|
15
|
+
options = DEFAULT_OPTIONS
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: #{$0} [options]"
|
18
|
+
|
19
|
+
opts.on("-v", "--verbose", "Verbose logging") do |v|
|
20
|
+
options[:verbose] = v
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-c", "--config", "Path to configuration file") do |c|
|
24
|
+
options[:config] = c
|
25
|
+
end
|
26
|
+
end.parse!
|
27
|
+
|
28
|
+
Logger.info( "Starting PostPolicy #{PostPolicy::VERSION::STRING}"
|
29
|
+
PostPolicy::Config.load_from_file( options[:config] )
|
30
|
+
PostPolicy::Protocol.new.start!
|
31
|
+
rescue
|
32
|
+
Logger.error( $!.message )
|
33
|
+
raise $!
|
34
|
+
end
|
data/dbconfig.yml
ADDED
data/lib/postpolicy.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
$:.unshift File.dirname( __FILE__ )
|
5
|
+
|
6
|
+
require 'postpolicy/config'
|
7
|
+
require 'postpolicy/protocol'
|
8
|
+
require 'postpolicy/access_manager'
|
9
|
+
require 'postpolicy/logger'
|
10
|
+
require 'postpolicy/extensions'
|
11
|
+
require 'postpolicy/server'
|
12
|
+
require 'postpolicy/rule'
|
13
|
+
require 'postpolicy/version'
|
14
|
+
|
15
|
+
VERBOSE = false unless defined?( VERBOSE )
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module PostPolicy
|
5
|
+
|
6
|
+
class AccessManager
|
7
|
+
|
8
|
+
include EventMachine::Deferrable
|
9
|
+
|
10
|
+
DEFAULT_ACTION = "DUNNO"
|
11
|
+
|
12
|
+
@@access_list = []
|
13
|
+
|
14
|
+
def self.access_list
|
15
|
+
@@access_list
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.<<( action )
|
19
|
+
@@access_list << action
|
20
|
+
end
|
21
|
+
|
22
|
+
def check( args )
|
23
|
+
action = DEFAULT_ACTION
|
24
|
+
@@access_list.each do |access|
|
25
|
+
if access.match?( args )
|
26
|
+
action = access.action
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
yield action if block_given?
|
31
|
+
return action
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
Dir.glob( File.join( File.dirname( __FILE__ ), "plugins/base/*" ) ).each { |base| require base }
|
5
|
+
Dir.glob( File.join( File.dirname( __FILE__ ), "plugins/acl/*" ) ).each { |acl| require acl }
|
6
|
+
Dir.glob( File.join( File.dirname( __FILE__ ), "plugins/datasource/*" ) ).each { |datasource| require datasource }
|
7
|
+
|
8
|
+
module PostPolicy
|
9
|
+
|
10
|
+
class Config
|
11
|
+
|
12
|
+
def self.load_from_file( filename )
|
13
|
+
load( YAML.load_file( filename ) )
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load( config_hash )
|
17
|
+
acls = Hash.new { |hsh, key| hsh[key] = Array.new }
|
18
|
+
config_acls = config_hash.delete( "acl" )
|
19
|
+
config_acls.each_pair do |human_name, klass_value_maps|
|
20
|
+
klass_value_maps.each_pair do |klass, value|
|
21
|
+
acls[human_name] << ACL.const_get(klass.classify).new( resolve_datasource( value ) )
|
22
|
+
end
|
23
|
+
end
|
24
|
+
actions = config_hash.delete( "action" )
|
25
|
+
accesses = config_hash.delete( "access" )
|
26
|
+
accesses.each_pair do |action, acl|
|
27
|
+
AccessManager << Access.new( acls[acl], actions[action] )
|
28
|
+
end
|
29
|
+
Db.load( config_hash["database"] )
|
30
|
+
end
|
31
|
+
|
32
|
+
class Db
|
33
|
+
class << self
|
34
|
+
attr_reader :dbconfig, :dbi_params
|
35
|
+
|
36
|
+
def load( filename )
|
37
|
+
if filename
|
38
|
+
@dbconfig = YAML.load_file( filename ).stringify_keys!.freeze
|
39
|
+
require "do_#{@dbconfig[:driver]}"
|
40
|
+
@dbi_params = "#{@dbconfig[:driver]}://#{@dbconfig[:user]}:#{@dbconfig[:password]}@#{@dbconfig[:host]}:#{@dbconfig[:port]}/#{@dbconfig[:database]}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def self.resolve_datasource( klass_and_value )
|
50
|
+
@@datasource_cache ||= {}
|
51
|
+
klass, value = klass_and_value.split( "://" )
|
52
|
+
if value == nil # consider as constant value
|
53
|
+
value = klass
|
54
|
+
klass = "value"
|
55
|
+
end
|
56
|
+
@@datasource_cache[klass] ||= DataSource.const_get(klass.classify)
|
57
|
+
|
58
|
+
return @@datasource_cache[klass].new( value )
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'syslog'
|
2
|
+
|
3
|
+
module Logger
|
4
|
+
|
5
|
+
@@app_name = "rpolicy"
|
6
|
+
@@log_opts = (Syslog::LOG_PID | Syslog::LOG_CONS)
|
7
|
+
@@facility = Syslog::LOG_MAIL
|
8
|
+
Syslog.open( @@app_name, @@log_opts, @@facility )
|
9
|
+
|
10
|
+
def self.error( msg )
|
11
|
+
Syslog.err( msg )
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.info( msg )
|
15
|
+
Syslog.info( msg )
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.debug( msg )
|
19
|
+
Syslog.debug( msg )
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.warn( msg )
|
23
|
+
Syslog.warning( msg )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module PostPolicy
|
2
|
+
module DataSource
|
3
|
+
|
4
|
+
class Sql < Base
|
5
|
+
|
6
|
+
def initialize( query )
|
7
|
+
@query = query
|
8
|
+
end
|
9
|
+
|
10
|
+
def exists?( value )
|
11
|
+
result = false
|
12
|
+
connection do |conn|
|
13
|
+
command = conn.create_command( @query )
|
14
|
+
reader = command.execute_reader
|
15
|
+
while reader.next!
|
16
|
+
if reader.values[0] && reader.values[0] == value
|
17
|
+
result = true
|
18
|
+
break
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def connection( &block )
|
27
|
+
conn = DataObjects::Connection.new( Config::Db.dbi_params )
|
28
|
+
block.call( conn )
|
29
|
+
conn.close
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module PostPolicy
|
4
|
+
class Protocol
|
5
|
+
|
6
|
+
PROTOCOLS = %w(ESMTP SMTP)
|
7
|
+
STATES = %w(CONNECT EHLO HELO MAIL RCPT DATA END-OF-MESSAGE VRFY ETRN)
|
8
|
+
|
9
|
+
TERMINATOR = "\n\n"
|
10
|
+
|
11
|
+
attr_reader :attributes
|
12
|
+
|
13
|
+
@@required_request_attributes = [:request, :protocol_state, :protocol_name, :helo_name, :queue_id, :sender, :recipient, :recipient_count, :client_address, :client_name, :reverse_client_name, :instance].to_set # Postfix 2.1 and later
|
14
|
+
|
15
|
+
@@request_attributes = @@required_request_attributes +
|
16
|
+
[:sasl_method, :sasl_username, :sasl_sender, :size, :ccert_subject, :ccert_issuer, :ccert_fingerprint, # Postifx 2.2 and later
|
17
|
+
:encryption_protocol, :encryption_cipher, :encryption_keysize, :etrn_domain, # Postifx 2.3 and later
|
18
|
+
:stress].to_set # Postifx 2.5 and later
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@attributes = {}
|
22
|
+
$stdout.sync = true
|
23
|
+
@buffer = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def start!
|
27
|
+
while line = gets do
|
28
|
+
receive_line( line.chomp )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def receive_line( line )
|
33
|
+
unless line.empty?
|
34
|
+
@buffer << line
|
35
|
+
else
|
36
|
+
Logger.info @buffer.inspect if VERBOSE
|
37
|
+
parse_request
|
38
|
+
@buffer.clear
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def response( action )
|
43
|
+
puts "action=#{action}#{TERMINATOR}" unless POST_POLICY_ENV == 'test'
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def parse_request
|
48
|
+
@buffer.each do |line|
|
49
|
+
key, value = line.split( '=' )
|
50
|
+
@attributes[key.to_sym] = value
|
51
|
+
end
|
52
|
+
return false if( sanitize_arguments == false || validate_arguments == false )
|
53
|
+
run_actions
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_actions
|
57
|
+
am = PostPolicy::AccessManager.new
|
58
|
+
am.callback do |args|
|
59
|
+
am.check( args ) do |action|
|
60
|
+
Logger.debug "Returning response #{action}" if VERBOSE
|
61
|
+
response( action )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
am.set_deferred_status :succeeded, @attributes
|
65
|
+
end
|
66
|
+
|
67
|
+
def sanitize_arguments
|
68
|
+
missing_attributes = @@required_request_attributes - @attributes.keys.to_set
|
69
|
+
unless missing_attributes.empty?
|
70
|
+
Logger.err "missing #{missing_attributes.to_a.join( ',' )}"
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
unknown_attributes = @attributes.keys.to_set - @@request_attributes
|
74
|
+
unless unknown_attributes.empty?
|
75
|
+
Logger.warn( 'Unknown attributes in policy request %s' % unknown_attributes.to_a.join( ',' ) ) if VERBOSE
|
76
|
+
end
|
77
|
+
unknown_attributes.each { |attr| @attributes.delete( attr ) }
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def validate_arguments
|
83
|
+
unless @attributes[:request] == "smtpd_access_policy"
|
84
|
+
Logger.err( 'only smtpd_access_policy request allowed' )
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
unless PROTOCOLS.include?( @attributes[:protocol_name] )
|
88
|
+
Logger.err( "only #{PROTOCOLS.join(',')} protocols allowed" )
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
unless STATES.include?( @attributes[:protocol_state] )
|
92
|
+
Logger.err( "only #{STATES.join(',')} states allowed" )
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module PostPolicy
|
2
|
+
|
3
|
+
# rule do
|
4
|
+
# sender { format.value "michal.lomnicki@gmail.com" }
|
5
|
+
# recipient { format.regex /lomnicki.com.pl$/ }
|
6
|
+
# action "REJECT"
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
class Rule
|
10
|
+
class Format
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def format
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def value( val )
|
19
|
+
DataSource::Value.new( val )
|
20
|
+
end
|
21
|
+
|
22
|
+
def regex( val )
|
23
|
+
DataSource::Regex.new( val )
|
24
|
+
end
|
25
|
+
|
26
|
+
def file( val )
|
27
|
+
DataSource::File.new( val )
|
28
|
+
end
|
29
|
+
|
30
|
+
def sql( val )
|
31
|
+
DataSource::Sql.new( val )
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize( &block )
|
39
|
+
instance_eval &block
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.rule( &block )
|
43
|
+
AccessManager << new( &block ).to_access
|
44
|
+
end
|
45
|
+
|
46
|
+
def sender( &block )
|
47
|
+
@rule_sender = ACL::Sender.new( Format.class_eval( &block ) )
|
48
|
+
end
|
49
|
+
|
50
|
+
def recipient( &block )
|
51
|
+
@rule_recipient = ACL::Recipient.new( Format.class_eval( &block ) )
|
52
|
+
end
|
53
|
+
|
54
|
+
def action( value )
|
55
|
+
@rule_action = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_access
|
59
|
+
Access.new( [@rule_sender, @rule_recipient].compact, @rule_action )
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# the simplest PostPolicy server using DSL
|
2
|
+
#
|
3
|
+
# require 'post_policy'
|
4
|
+
#
|
5
|
+
# PostPolicy::Server.run do
|
6
|
+
#
|
7
|
+
# rule do
|
8
|
+
# sender { format.value "michal.lomnicki@gmail.com" }
|
9
|
+
# action "REJECT"
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# end
|
13
|
+
module PostPolicy
|
14
|
+
|
15
|
+
class Server
|
16
|
+
|
17
|
+
def self.run( &block )
|
18
|
+
Rule.class_eval( &block )
|
19
|
+
PostPolicy::Protocol.new.start!
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/post_policy.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{post_policy}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Micha\305\202 \305\201omnicki"]
|
12
|
+
s.date = %q{2009-10-12}
|
13
|
+
s.default_executable = %q{postpolicy.rb}
|
14
|
+
s.description = %q{PostPolicy uses ACL system, which allow administrators to create rules based on mail source.}
|
15
|
+
s.email = %q{michal@lomnicki.com.pl}
|
16
|
+
s.executables = ["postpolicy.rb"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".gitignore",
|
22
|
+
"CHANGELOG",
|
23
|
+
"MIT-LICENSE",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/postpolicy.rb",
|
28
|
+
"dbconfig.yml",
|
29
|
+
"lib/postpolicy.rb",
|
30
|
+
"lib/postpolicy/access_manager.rb",
|
31
|
+
"lib/postpolicy/config.rb",
|
32
|
+
"lib/postpolicy/extensions.rb",
|
33
|
+
"lib/postpolicy/extensions/hash.rb",
|
34
|
+
"lib/postpolicy/extensions/string.rb",
|
35
|
+
"lib/postpolicy/logger.rb",
|
36
|
+
"lib/postpolicy/plugins/acl/recipient.rb",
|
37
|
+
"lib/postpolicy/plugins/acl/sender.rb",
|
38
|
+
"lib/postpolicy/plugins/base/access.rb",
|
39
|
+
"lib/postpolicy/plugins/base/acl.rb",
|
40
|
+
"lib/postpolicy/plugins/base/datasource.rb",
|
41
|
+
"lib/postpolicy/plugins/datasource/file.rb",
|
42
|
+
"lib/postpolicy/plugins/datasource/regex.rb",
|
43
|
+
"lib/postpolicy/plugins/datasource/sql.rb",
|
44
|
+
"lib/postpolicy/plugins/datasource/value.rb",
|
45
|
+
"lib/postpolicy/protocol.rb",
|
46
|
+
"lib/postpolicy/rule.rb",
|
47
|
+
"lib/postpolicy/server.rb",
|
48
|
+
"lib/postpolicy/version.rb",
|
49
|
+
"post_policy.gemspec",
|
50
|
+
"postpolicy.yml",
|
51
|
+
"spec/access_manager_spec.rb",
|
52
|
+
"spec/acl_spec.rb",
|
53
|
+
"spec/config_spec.rb",
|
54
|
+
"spec/datasource_spec.rb",
|
55
|
+
"spec/format_spec.rb",
|
56
|
+
"spec/protocol_spec.rb",
|
57
|
+
"spec/rule_spec.rb",
|
58
|
+
"spec/test_helper.rb"
|
59
|
+
]
|
60
|
+
s.homepage = %q{http://github.com/mlomnicki/post_policy}
|
61
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
62
|
+
s.require_paths = ["lib"]
|
63
|
+
s.rubygems_version = %q{1.3.5}
|
64
|
+
s.summary = %q{Postfix Policy Server in Ruby}
|
65
|
+
s.test_files = [
|
66
|
+
"spec/acl_spec.rb",
|
67
|
+
"spec/access_manager_spec.rb",
|
68
|
+
"spec/format_spec.rb",
|
69
|
+
"spec/datasource_spec.rb",
|
70
|
+
"spec/protocol_spec.rb",
|
71
|
+
"spec/test_helper.rb",
|
72
|
+
"spec/rule_spec.rb",
|
73
|
+
"spec/config_spec.rb"
|
74
|
+
]
|
75
|
+
|
76
|
+
if s.respond_to? :specification_version then
|
77
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
78
|
+
s.specification_version = 3
|
79
|
+
|
80
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
81
|
+
else
|
82
|
+
end
|
83
|
+
else
|
84
|
+
end
|
85
|
+
end
|
data/postpolicy.yml
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
CONFIG = {
|
4
|
+
"acl" => {
|
5
|
+
"michal-lomnicki" => {
|
6
|
+
"sender" => "michal@lomnicki.com.pl"
|
7
|
+
},
|
8
|
+
"foo-to-bar" => {
|
9
|
+
"sender" => "foo",
|
10
|
+
"recipient" => "bar"
|
11
|
+
}
|
12
|
+
},
|
13
|
+
|
14
|
+
"action" => { "reject" => "REJECT", "defer" => "DEFER_IF_PERMIT" },
|
15
|
+
"access" => {
|
16
|
+
"reject" => "michal-lomnicki",
|
17
|
+
"defer" => "foo-to-bar"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
describe PostPolicy::AccessManager do
|
22
|
+
|
23
|
+
before(:all) do
|
24
|
+
PostPolicy::Config.load( CONFIG )
|
25
|
+
@am = PostPolicy::AccessManager.new
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should reject michal@lomnicki.com.pl" do
|
29
|
+
@am.check( { :sender => "michal@lomnicki.com.pl" } ) do |action|
|
30
|
+
action.should == "REJECT"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should defer foo to bar" do
|
35
|
+
@am.check( { :sender => "foo", :recipient => "bar" } ) do |action|
|
36
|
+
action.should == "DEFER_IF_PERMIT"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# on any other acl default action should be given
|
41
|
+
it "should return default action on bar to foo" do
|
42
|
+
@am.check( { :sender => "bar", :recipient => "foo" } ) do |action|
|
43
|
+
action.should == PostPolicy::AccessManager::DEFAULT_ACTION
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/spec/acl_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe PostPolicy::ACL::Sender do
|
4
|
+
|
5
|
+
it "should match given sender" do
|
6
|
+
ds = PostPolicy::DataSource::Value.new( ATTRS[:sender] )
|
7
|
+
sender_acl = PostPolicy::ACL::Sender.new( ds )
|
8
|
+
sender_acl.match?( ATTRS ).should == true
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe PostPolicy::ACL::Recipient do
|
14
|
+
|
15
|
+
it "should match given recipient" do
|
16
|
+
ds = PostPolicy::DataSource::Value.new( ATTRS[:recipient] )
|
17
|
+
recipient_acl = PostPolicy::ACL::Recipient.new( ds )
|
18
|
+
recipient_acl.match?( ATTRS ).should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe PostPolicy::Config::Db do
|
4
|
+
|
5
|
+
it "should load config from file and build dbi_params" do
|
6
|
+
filename = '/tmp/dbconfig'
|
7
|
+
config = { :host => 'localhost',
|
8
|
+
:database => 'postpolicy',
|
9
|
+
:user => 'foo',
|
10
|
+
:password => 'secret',
|
11
|
+
:port => 5432,
|
12
|
+
:driver => 'postgres' }
|
13
|
+
|
14
|
+
File.open( filename, 'w' ) do |f|
|
15
|
+
f.puts config.to_yaml
|
16
|
+
end
|
17
|
+
|
18
|
+
PostPolicy::Config::Db.load( filename )
|
19
|
+
PostPolicy::Config::Db.dbconfig.should == config
|
20
|
+
dbi_params = "#{config[:driver]}://#{config[:user]}:#{config[:password]}@#{config[:host]}:#{config[:port]}/#{config[:database]}"
|
21
|
+
PostPolicy::Config::Db.dbi_params.should == dbi_params
|
22
|
+
|
23
|
+
FileUtils.rm( filename )
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
GOOD_VALUES = ["michal@lomnicki.com.pl", "foo@example.com", "bar@baz.com"]
|
4
|
+
BAD_VALUES = ["michal@bar.com", "bar@baz.net", "foo@bar.baz"]
|
5
|
+
|
6
|
+
describe PostPolicy::DataSource::Value do
|
7
|
+
|
8
|
+
it "should allow passing one or multiple values" do
|
9
|
+
ds = PostPolicy::DataSource::Value.new( 1 )
|
10
|
+
ds.exists?( 1 ).should == true
|
11
|
+
ds = PostPolicy::DataSource::Value.new( [1,2] )
|
12
|
+
ds.exists?( 1 ).should == true
|
13
|
+
ds.exists?( 2 ).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should match initialized dss" do
|
17
|
+
ds = PostPolicy::DataSource::Value.new( GOOD_VALUES )
|
18
|
+
ds.exists?( GOOD_VALUES.first ).should == true
|
19
|
+
ds.exists?( GOOD_VALUES.last ).should == true
|
20
|
+
ds.exists?( BAD_VALUES.first ).should == false
|
21
|
+
ds.exists?( BAD_VALUES.last ).should == false
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe PostPolicy::DataSource::File do
|
27
|
+
|
28
|
+
it "should read from file" do
|
29
|
+
filename = '/tmp/file_datasource'
|
30
|
+
File.open( filename, 'w' ) do |f|
|
31
|
+
GOOD_VALUES.each { |value| f.puts value }
|
32
|
+
end
|
33
|
+
ds = PostPolicy::DataSource::File.new( filename )
|
34
|
+
GOOD_VALUES.each do |value|
|
35
|
+
ds.exists?( value ).should == true
|
36
|
+
end
|
37
|
+
BAD_VALUES.each do |value|
|
38
|
+
ds.exists?( value ).should == false
|
39
|
+
end
|
40
|
+
FileUtils.rm( filename )
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe PostPolicy::DataSource::Regex do
|
46
|
+
|
47
|
+
it "should match regex" do
|
48
|
+
ds = PostPolicy::DataSource::Regex.new( /^michal@.*\.pl$/ )
|
49
|
+
ds.exists?( "michal@lomnicki.com.pl" ).should == true
|
50
|
+
ds.exists?( "foo@lomnicki.com.pl" ).should == false
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe PostPolicy::DataSource::Sql do
|
56
|
+
|
57
|
+
it "should check in db" do
|
58
|
+
ds = PostPolicy::DataSource::Sql.new( "SELECT id from test" )
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/spec/format_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe PostPolicy::Rule::Format do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@klass = PostPolicy::Rule::Format
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return DataSource::Value for format.value" do
|
10
|
+
value = "michal.lomnicki@gmail.com"
|
11
|
+
ds = @klass.format.value( value )
|
12
|
+
ds.class.should == PostPolicy::DataSource::Value
|
13
|
+
ds.exists?( value ).should == true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return DataSource::Regex for format.regex" do
|
17
|
+
value = /gmail.com$/
|
18
|
+
ds = @klass.format.regex( value )
|
19
|
+
ds.class.should == PostPolicy::DataSource::Regex
|
20
|
+
ds.exists?( "michal.lomnicki@gmail.com" ).should == true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return DataSource::File for format.file" do
|
24
|
+
filename = '/tmp/file_datasource'
|
25
|
+
value = "michal.lomnicki@gmail.com"
|
26
|
+
File.open( filename, 'w' ) do |f|
|
27
|
+
f.puts value
|
28
|
+
end
|
29
|
+
ds = @klass.format.file( filename )
|
30
|
+
ds.class.should == PostPolicy::DataSource::File
|
31
|
+
ds.exists?( value ).should == true
|
32
|
+
FileUtils.rm( filename )
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
describe PostPolicy::Protocol do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@protocol = PostPolicy::Protocol.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse attributes" do
|
11
|
+
form_string( ATTRS ).each_line { |l| @protocol.receive_line( l.strip ) }
|
12
|
+
@protocol.attributes.should == ATTRS
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should discard unknown attributes" do
|
16
|
+
ATTRS.merge( :foo => 'bar', :bar => 'baz' ).each_pair { |k,v| @protocol.receive_line( [k,v].join( '=' ) ) }
|
17
|
+
form_string( ATTRS ).each_line { |l| @protocol.receive_line( l.strip ) }
|
18
|
+
@protocol.attributes.should == ATTRS
|
19
|
+
end
|
20
|
+
|
21
|
+
#it "should return false on missing attributes" do
|
22
|
+
# ATTRS.reject { |k,v| k == :protocol_name }.each_pair { |k,v| @protocol.receive_line( [k,v].join( '=' ) ) }
|
23
|
+
# form_string( ATTRS ).each_line { |l| @protocol.receive_line( l.strip ) }
|
24
|
+
# @protocol.receive.should == false
|
25
|
+
#end
|
26
|
+
|
27
|
+
#it "should validate arguments" do
|
28
|
+
# ATTRS.merge( :request => "bar" ).each_pair { |k,v| @protocol.receive_line( [k,v].join( '=' ) ) }
|
29
|
+
# form_string( ATTRS ).each_line { |l| @protocol.receive_line( l.strip ) }
|
30
|
+
# @protocol.receive.should == false
|
31
|
+
#end
|
32
|
+
|
33
|
+
#it "should terminate response" do
|
34
|
+
# @protocol.response("dunno").should == "action=dunno\n\n"
|
35
|
+
#end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def form_string( args )
|
39
|
+
StringIO.new( args.each.inject("") { |str, kv| str << (kv.join('=') + "\n") } << "\n" )
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe PostPolicy::Rule do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
SENDER = "foo@example.com"
|
7
|
+
RECIPIENT = "bar@example.com"
|
8
|
+
ACTION = "REJECT"
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@rule = PostPolicy::Rule.new do
|
13
|
+
sender { format.value SENDER }
|
14
|
+
recipient { format.value RECIPIENT }
|
15
|
+
action ACTION
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should respond_to to_access" do
|
21
|
+
@rule.should respond_to( :to_access )
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should match given arguments after converting to access" do
|
25
|
+
@rule.to_access.match?( :sender => SENDER, :recipient => RECIPIENT ).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return given action after converting to access" do
|
29
|
+
@rule.to_access.action.should == ACTION
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift File.join( File.dirname( __FILE__ ), '../lib' )
|
2
|
+
require 'postpolicy'
|
3
|
+
|
4
|
+
POST_POLICY_ENV = ENV['POST_POLICY_ENV'] || "test"
|
5
|
+
|
6
|
+
ATTRS = {
|
7
|
+
:request => "smtpd_access_policy",
|
8
|
+
:protocol_state => "RCPT",
|
9
|
+
:protocol_name => "SMTP",
|
10
|
+
:helo_name => "some.domain.tld",
|
11
|
+
:queue_id => "8045F2AB23",
|
12
|
+
:sender => "foo@bar.tld",
|
13
|
+
:recipient => "bar@foo.tld",
|
14
|
+
:recipient_count => "0",
|
15
|
+
:client_address => "1.2.3.4",
|
16
|
+
:client_name => "another.domain.tld",
|
17
|
+
:reverse_client_name => "another.domain.tld",
|
18
|
+
:instance => "123.456.7"
|
19
|
+
}
|
20
|
+
|
21
|
+
Object.send(:remove_const, :Logger)
|
22
|
+
|
23
|
+
module Logger
|
24
|
+
def self.method_missing(symbol, *args)
|
25
|
+
#simpy do nothing
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: post_policy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Micha\xC5\x82 \xC5\x81omnicki"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-12 00:00:00 +02:00
|
13
|
+
default_executable: postpolicy.rb
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: PostPolicy uses ACL system, which allow administrators to create rules based on mail source.
|
17
|
+
email: michal@lomnicki.com.pl
|
18
|
+
executables:
|
19
|
+
- postpolicy.rb
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- CHANGELOG
|
27
|
+
- MIT-LICENSE
|
28
|
+
- README.rdoc
|
29
|
+
- Rakefile
|
30
|
+
- VERSION
|
31
|
+
- bin/postpolicy.rb
|
32
|
+
- dbconfig.yml
|
33
|
+
- lib/postpolicy.rb
|
34
|
+
- lib/postpolicy/access_manager.rb
|
35
|
+
- lib/postpolicy/config.rb
|
36
|
+
- lib/postpolicy/extensions.rb
|
37
|
+
- lib/postpolicy/extensions/hash.rb
|
38
|
+
- lib/postpolicy/extensions/string.rb
|
39
|
+
- lib/postpolicy/logger.rb
|
40
|
+
- lib/postpolicy/plugins/acl/recipient.rb
|
41
|
+
- lib/postpolicy/plugins/acl/sender.rb
|
42
|
+
- lib/postpolicy/plugins/base/access.rb
|
43
|
+
- lib/postpolicy/plugins/base/acl.rb
|
44
|
+
- lib/postpolicy/plugins/base/datasource.rb
|
45
|
+
- lib/postpolicy/plugins/datasource/file.rb
|
46
|
+
- lib/postpolicy/plugins/datasource/regex.rb
|
47
|
+
- lib/postpolicy/plugins/datasource/sql.rb
|
48
|
+
- lib/postpolicy/plugins/datasource/value.rb
|
49
|
+
- lib/postpolicy/protocol.rb
|
50
|
+
- lib/postpolicy/rule.rb
|
51
|
+
- lib/postpolicy/server.rb
|
52
|
+
- lib/postpolicy/version.rb
|
53
|
+
- post_policy.gemspec
|
54
|
+
- postpolicy.yml
|
55
|
+
- spec/access_manager_spec.rb
|
56
|
+
- spec/acl_spec.rb
|
57
|
+
- spec/config_spec.rb
|
58
|
+
- spec/datasource_spec.rb
|
59
|
+
- spec/format_spec.rb
|
60
|
+
- spec/protocol_spec.rb
|
61
|
+
- spec/rule_spec.rb
|
62
|
+
- spec/test_helper.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/mlomnicki/post_policy
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options:
|
69
|
+
- --charset=UTF-8
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Postfix Policy Server in Ruby
|
91
|
+
test_files:
|
92
|
+
- spec/acl_spec.rb
|
93
|
+
- spec/access_manager_spec.rb
|
94
|
+
- spec/format_spec.rb
|
95
|
+
- spec/datasource_spec.rb
|
96
|
+
- spec/protocol_spec.rb
|
97
|
+
- spec/test_helper.rb
|
98
|
+
- spec/rule_spec.rb
|
99
|
+
- spec/config_spec.rb
|