post_policy 0.0.1
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.
- 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
|