milter 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 +17 -0
- data/Gemfile +4 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/examples/body_filter.rb +18 -0
- data/examples/logging.rb +70 -0
- data/lib/milter.rb +330 -0
- data/lib/milter/version.rb +3 -0
- data/milter.gemspec +24 -0
- metadata +109 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Milter
|
2
|
+
|
3
|
+
This software originated as a clone of ppymilter (http://code.google.com/p/ppymilter/) by Hirohisa Mitsuishi (https://github.com/bongole).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'milter'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install milter
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'milter'
|
2
|
+
|
3
|
+
# example
|
4
|
+
# change mail body
|
5
|
+
class MyMilter < Milter::Milter
|
6
|
+
def header( k,v )
|
7
|
+
puts "#{k} => #{v}"
|
8
|
+
return Response.continue
|
9
|
+
end
|
10
|
+
|
11
|
+
def end_body( *args )
|
12
|
+
puts "BODY => #{@body}"
|
13
|
+
return [Response.replace_body("hogehoge"), Response.continue]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Milter.register(MyMilter)
|
18
|
+
Milter.start
|
data/examples/logging.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'milter'
|
2
|
+
|
3
|
+
class MyMilter < Milter::Milter
|
4
|
+
|
5
|
+
def connect( hostname, family, port, address )
|
6
|
+
puts "connect from #{address}:#{port} (#{hostname})."
|
7
|
+
return Response.continue
|
8
|
+
end
|
9
|
+
|
10
|
+
def helo( text )
|
11
|
+
puts "helo #{text}"
|
12
|
+
return Response.continue
|
13
|
+
end
|
14
|
+
|
15
|
+
def mail_from( mailfrom, esmtp_info )
|
16
|
+
puts "mail_from: #{mailfrom}"
|
17
|
+
return Response.continue
|
18
|
+
end
|
19
|
+
|
20
|
+
def rcpt_to( mailto, esmtp_info )
|
21
|
+
puts "rcpt_to: #{mailto} (#{esmtp_info})"
|
22
|
+
return Response.continue
|
23
|
+
end
|
24
|
+
|
25
|
+
def data
|
26
|
+
puts "data"
|
27
|
+
return Response.continue
|
28
|
+
end
|
29
|
+
|
30
|
+
def unknown( data )
|
31
|
+
puts "unknown smtp command: #{data}"
|
32
|
+
return Response.continue
|
33
|
+
end
|
34
|
+
|
35
|
+
def header( k, v )
|
36
|
+
puts "header: #{k} => #{v}"
|
37
|
+
@headers[k] = [] if @headers[k].nil?
|
38
|
+
@headers[k] << v
|
39
|
+
return Response.continue
|
40
|
+
end
|
41
|
+
|
42
|
+
def end_headers
|
43
|
+
puts "end of headers; headers: #{@headers.inspect}"
|
44
|
+
return Response.continue
|
45
|
+
end
|
46
|
+
|
47
|
+
def end_body( data )
|
48
|
+
puts "end of body: #{data}; body: #{@body}"
|
49
|
+
return Response.continue
|
50
|
+
end
|
51
|
+
|
52
|
+
def abort
|
53
|
+
puts "abort"
|
54
|
+
return Response.continue
|
55
|
+
end
|
56
|
+
|
57
|
+
def quit
|
58
|
+
puts "quit"
|
59
|
+
return Response.continue
|
60
|
+
end
|
61
|
+
|
62
|
+
def macro( cmd, data )
|
63
|
+
puts "#{Milter::COMMANDS[cmd]} macros: #{data.inspect}"
|
64
|
+
return Response.continue
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
Milter.register(MyMilter)
|
70
|
+
Milter.start
|
data/lib/milter.rb
ADDED
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'milter/version'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module Milter
|
5
|
+
SMFI_PROT_VERSION = 6 # "MTA - libmilter protocol version"
|
6
|
+
MILTER_LEN_BYTES = 4 # "length of 32 bit integer in bytes"
|
7
|
+
|
8
|
+
# Milter binary protocol commands
|
9
|
+
SMFIC_ABORT = 'A' # "Abort"
|
10
|
+
SMFIC_BODY = 'B' # "Body chunk"
|
11
|
+
SMFIC_CONNECT = 'C' # "Connection information"
|
12
|
+
SMFIC_MACRO = 'D' # "Define macro"
|
13
|
+
SMFIC_BODYEOB = 'E' # "final body chunk (End)"
|
14
|
+
SMFIC_HELO = 'H' # "HELO/EHLO"
|
15
|
+
SMFIC_QUIT_NC = 'K' # "QUIT but new connection follows"
|
16
|
+
SMFIC_HEADER = 'L' # "Header"
|
17
|
+
SMFIC_MAIL = 'M' # "MAIL from"
|
18
|
+
SMFIC_EOH = 'N' # "EOH"
|
19
|
+
SMFIC_OPTNEG = 'O' # "Option negotation"
|
20
|
+
SMFIC_QUIT = 'Q' # "QUIT"
|
21
|
+
SMFIC_RCPT = 'R' # "RCPT to"
|
22
|
+
SMFIC_DATA = 'T' # "DATA"
|
23
|
+
SMFIC_UNKNOWN = 'U' # "Any unknown command"
|
24
|
+
|
25
|
+
# mappings for Ruby callbacks
|
26
|
+
COMMANDS = {
|
27
|
+
SMFIC_ABORT => :abort,
|
28
|
+
SMFIC_BODY => :body,
|
29
|
+
SMFIC_CONNECT => :connect,
|
30
|
+
SMFIC_MACRO => :macro,
|
31
|
+
SMFIC_BODYEOB => :end_body,
|
32
|
+
SMFIC_HELO => :helo,
|
33
|
+
SMFIC_QUIT_NC => :quit_new_connection,
|
34
|
+
SMFIC_HEADER => :header,
|
35
|
+
SMFIC_MAIL => :mail_from,
|
36
|
+
SMFIC_EOH => :end_headers,
|
37
|
+
SMFIC_OPTNEG => :opt_neg,
|
38
|
+
SMFIC_QUIT => :quit,
|
39
|
+
SMFIC_RCPT => :rcpt_to,
|
40
|
+
SMFIC_DATA => :data,
|
41
|
+
SMFIC_UNKNOWN => :unknown,
|
42
|
+
}
|
43
|
+
|
44
|
+
# actions(replies)
|
45
|
+
RESPONSE = {
|
46
|
+
:addrcpt => '+', # SMFIR_ADDRCPT # "add recipient"
|
47
|
+
:delrcpt => '-', # SMFIR_DELRCPT # "remove recipient"
|
48
|
+
:addrcpt_par => '2', # SMFIR_ADDRCPT_PAR # "add recipient (incl. ESMTP args)"
|
49
|
+
:shutdown => '4', # SMFIR_SHUTDOWN # "421: shutdown (internal to MTA)"
|
50
|
+
:accept => 'a', # SMFIR_ACCEPT # "accept"
|
51
|
+
:replbody => 'b', # SMFIR_REPLBODY # "replace body (chunk)"
|
52
|
+
:continue => 'c', # SMFIR_CONTINUE # "continue"
|
53
|
+
:discard => 'd', # SMFIR_DISCARD # "discard"
|
54
|
+
:chgfrom => 'e', # SMFIR_CHGFROM # "change envelope sender (from)"
|
55
|
+
:connfail => 'f', # SMFIR_CONN_FAIL # "cause a connection failure"
|
56
|
+
:addheader => 'h', # SMFIR_ADDHEADER # "add header"
|
57
|
+
:insheader => 'i', # SMFIR_INSHEADER # "insert header"
|
58
|
+
:setsymlist => 'l', # SMFIR_SETSYMLIST # "set list of symbols (macros)"
|
59
|
+
:chgheader => 'm', # SMFIR_CHGHEADER # "change header"
|
60
|
+
:progress => 'p', # SMFIR_PROGRESS # "progress"
|
61
|
+
:quarantine => 'q', # SMFIR_QUARANTINE # "quarantine"
|
62
|
+
:reject => 'r', # SMFIR_REJECT # "reject"
|
63
|
+
:skip => 's', # SMFIR_SKIP # "skip"
|
64
|
+
:tempfail => 't', # SMFIR_TEMPFAIL # "tempfail"
|
65
|
+
:replycode => 'y', # SMFIR_REPLYCODE # "reply code etc"
|
66
|
+
}
|
67
|
+
|
68
|
+
# What the MTA can send/filter wants in protocol
|
69
|
+
PROTOCOL_FLAGS = {
|
70
|
+
:noconnect => 0x000001, # SMFIP_NOCONNECT # MTA should not send connect info
|
71
|
+
:nohelo => 0x000002, # SMFIP_NOHELO # MTA should not send HELO info
|
72
|
+
:nomail => 0x000004, # SMFIP_NOMAIL # MTA should not send MAIL info
|
73
|
+
:norcpt => 0x000008, # SMFIP_NORCPT # MTA should not send RCPT info
|
74
|
+
:nobody => 0x000010, # SMFIP_NOBODY # MTA should not send body
|
75
|
+
:nohdrs => 0x000020, # SMFIP_NOHDRS # MTA should not send headers
|
76
|
+
:noeoh => 0x000040, # SMFIP_NOEOH # MTA should not send EOH
|
77
|
+
:nohrepl => 0x000080, # SMFIP_NR_HDR # No reply for headers
|
78
|
+
:nounknown => 0x000100, # SMFIP_NOUNKNOWN # MTA should not send unknown commands
|
79
|
+
:nodata => 0x000200, # SMFIP_NODATA # MTA should not send DATA
|
80
|
+
:skip => 0x000400, # SMFIP_SKIP # MTA understands SMFIS_SKIP
|
81
|
+
:rcpt_rej => 0x000800, # SMFIP_RCPT_REJ # MTA should also send rejected RCPTs
|
82
|
+
:nr_conn => 0x001000, # SMFIP_NR_CONN # No reply for connect
|
83
|
+
:nr_helo => 0x002000, # SMFIP_NR_HELO # No reply for HELO
|
84
|
+
:nr_mail => 0x004000, # SMFIP_NR_MAIL # No reply for MAIL
|
85
|
+
:nr_rcpt => 0x008000, # SMFIP_NR_RCPT # No reply for RCPT
|
86
|
+
:nr_data => 0x010000, # SMFIP_NR_DATA # No reply for DATA
|
87
|
+
:nr_unkn => 0x020000, # SMFIP_NR_UNKN # No reply for UNKN
|
88
|
+
:nr_eoh => 0x040000, # SMFIP_NR_EOH # No reply for eoh
|
89
|
+
:nr_body => 0x080000, # SMFIP_NR_BODY # No reply for body chunk
|
90
|
+
:hrd_leadspc => 0x100000, # SMFIP_HDR_LEADSPC # header value leading space
|
91
|
+
}
|
92
|
+
|
93
|
+
# What the filter might do -- values to be ORed together
|
94
|
+
# (from mfapi.h)
|
95
|
+
ACTION_FLAGS = {
|
96
|
+
:none => 0x000, # SMFIF_NONE # no flags
|
97
|
+
:addhdrs => 0x001, # SMFIF_ADDHDRS # filter may add headers
|
98
|
+
:chgbody => 0x002, # SMFIF_CHGBODY # filter may replace body
|
99
|
+
:addrcpt => 0x004, # SMFIF_ADDRCPT # filter may add recipients
|
100
|
+
:delrcpt => 0x008, # SMFIF_DELRCPT # filter may delete recipients
|
101
|
+
:chghdrs => 0x010, # SMFIF_CHGHDRS # filter may change/delete headers
|
102
|
+
:quarantine => 0x020, # SMFIF_QUARANTINE # filter may quarantine envelope
|
103
|
+
:chgfrom => 0x040, # SMFIF_CHGFROM # filter may change "from" (envelope sender)
|
104
|
+
:addrcpt_par => 0x080, # SMFIF_ADDRCPT_PAR # add recipients incl. args
|
105
|
+
:setsymlist => 0x100, # SMFIF_SETSYMLIST # filter can send set of symbols (macros) that it wants
|
106
|
+
:set_curr => 0x1FF, # SMFI_CURR_ACTS # Set of all actions in the current milter version
|
107
|
+
}
|
108
|
+
|
109
|
+
class Milter
|
110
|
+
def initialize
|
111
|
+
@body = ''
|
112
|
+
@headers = {}
|
113
|
+
@recipients = []
|
114
|
+
end
|
115
|
+
|
116
|
+
def opt_neg( ver, actions, protocol )
|
117
|
+
puts "New Milter connection - version: %s, action flags: %x, protocol flags: %x." % [ver, actions, protocol]
|
118
|
+
_actions = ACTION_FLAGS[:set_curr] # allow all supported actions
|
119
|
+
# _actions = ACTION_FLAGS.values_at(:addhrds, :chgfrom).reduce(:+) # example for enabling individual actions
|
120
|
+
_protocol = PROTOCOL_FLAGS.values_at(:rcpt_rej).reduce(:+) # register callback for rejected recipients
|
121
|
+
return SMFIC_OPTNEG + [ SMFI_PROT_VERSION, _actions, _protocol].pack("NNN")
|
122
|
+
end
|
123
|
+
|
124
|
+
def rcpt_to( mailto, esmtp_info )
|
125
|
+
@recipients << mailto
|
126
|
+
return Response.continue
|
127
|
+
end
|
128
|
+
|
129
|
+
def header( k,v )
|
130
|
+
@headers[k] = [] if @headers[k].nil?
|
131
|
+
@headers[k] << v
|
132
|
+
return Response.continue
|
133
|
+
end
|
134
|
+
|
135
|
+
def body( data )
|
136
|
+
@body << data
|
137
|
+
return Response.continue
|
138
|
+
end
|
139
|
+
|
140
|
+
class Response
|
141
|
+
class << self
|
142
|
+
def continue
|
143
|
+
RESPONSE[:continue]
|
144
|
+
end
|
145
|
+
|
146
|
+
def reject
|
147
|
+
RESPONSE[:reject]
|
148
|
+
end
|
149
|
+
|
150
|
+
def accept
|
151
|
+
RESPONSE[:accept]
|
152
|
+
end
|
153
|
+
|
154
|
+
#email must be enclosed in <>
|
155
|
+
def delete_rcpt( email )
|
156
|
+
RESPONSE[:delrcpt] + email + "\0"
|
157
|
+
end
|
158
|
+
|
159
|
+
#email must be enclosed in <>
|
160
|
+
def add_rcpt( email )
|
161
|
+
RESPONSE[:addrcpt] + email + "\0"
|
162
|
+
end
|
163
|
+
|
164
|
+
#index is for multiple occurences of same header (starts at 1)
|
165
|
+
def change_header( header, value, index=1 )
|
166
|
+
index = [index].pack('N')
|
167
|
+
RESPONSE[:chgheader] + "#{index}#{header}\0#{value}" + "\0"
|
168
|
+
end
|
169
|
+
|
170
|
+
def replace_body( body )
|
171
|
+
RESPONSE[:replbody] + body + "\0"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class MilterConnectionHandler < EM::Connection
|
178
|
+
@@milter_class = Milter
|
179
|
+
|
180
|
+
def initialize
|
181
|
+
@data = ''
|
182
|
+
@milter = @@milter_class.new
|
183
|
+
end
|
184
|
+
|
185
|
+
def send_milter_response( res )
|
186
|
+
r = [ res.size ].pack('N') + res
|
187
|
+
send_data(r)
|
188
|
+
end
|
189
|
+
|
190
|
+
# SMFIC_OPTNEG, two integers
|
191
|
+
def parse_opt_neg( data )
|
192
|
+
ver, actions, protocol = data.unpack('NNN')
|
193
|
+
return [ver, actions, protocol]
|
194
|
+
end
|
195
|
+
|
196
|
+
# SMFIC_MACRO, \0 separated list of args, NULL-terminated
|
197
|
+
def parse_macro( data )
|
198
|
+
cmd, macros = data[0].chr, data[1..-1].split("\0" )
|
199
|
+
return [cmd, Hash[*macros]]
|
200
|
+
end
|
201
|
+
|
202
|
+
# SMFIC_CONNECT, two args (strings)
|
203
|
+
def parse_connect( data )
|
204
|
+
hostname, val = data.split("\0", 2)
|
205
|
+
family = val[0].unpack('C')
|
206
|
+
port = val[1...3].unpack('n')
|
207
|
+
address = val[3..-1]
|
208
|
+
return [hostname, family, port, address]
|
209
|
+
end
|
210
|
+
|
211
|
+
# SMFIC_HELO, one arg (string)
|
212
|
+
def parse_helo( data )
|
213
|
+
return [data]
|
214
|
+
end
|
215
|
+
|
216
|
+
# SMFIC_MAIL, \0 separated list of args, NULL-terminated
|
217
|
+
def parse_mail_from( data )
|
218
|
+
mailfrom, esmtp_info = data.split("\0", 2 )
|
219
|
+
return [mailfrom, esmtp_info.split("\0")]
|
220
|
+
end
|
221
|
+
|
222
|
+
# SMFIC_RCPT, \0 separated list of args, NULL-terminated
|
223
|
+
def parse_rcpt_to( data )
|
224
|
+
mailto, esmtp_info = data.split("\0", 2 )
|
225
|
+
return [mailto, esmtp_info.split("\0")]
|
226
|
+
end
|
227
|
+
|
228
|
+
# SMFIC_HEADER, two args (strings)
|
229
|
+
def parse_header( data )
|
230
|
+
k,v = data.split("\0", 2)
|
231
|
+
return [k, v.delete("\0")]
|
232
|
+
end
|
233
|
+
|
234
|
+
# SMFIC_EOH, no args
|
235
|
+
def parse_end_headers( data )
|
236
|
+
return []
|
237
|
+
end
|
238
|
+
|
239
|
+
# SMFIC_BODY, one arg (string)
|
240
|
+
def parse_body( data )
|
241
|
+
return [ data.delete("\0") ]
|
242
|
+
end
|
243
|
+
|
244
|
+
# SMFIC_BODYEOB, one arg (string)
|
245
|
+
def parse_end_body( data )
|
246
|
+
return [data]
|
247
|
+
end
|
248
|
+
|
249
|
+
# SMFIC_QUIT, no args
|
250
|
+
def prase_quit( data )
|
251
|
+
return []
|
252
|
+
end
|
253
|
+
|
254
|
+
# SMFIC_ABORT, no args
|
255
|
+
def parse_abort( data )
|
256
|
+
return []
|
257
|
+
end
|
258
|
+
|
259
|
+
# SMFIC_DATA, no args
|
260
|
+
def parse_data( data )
|
261
|
+
return []
|
262
|
+
end
|
263
|
+
|
264
|
+
# SMFIC_UNKNOWN, one arg (string)
|
265
|
+
def parse_unknown( data )
|
266
|
+
return data
|
267
|
+
end
|
268
|
+
|
269
|
+
# SMFIC_QUIT_NC, no args
|
270
|
+
def parse_quit_new_connection( data )
|
271
|
+
return []
|
272
|
+
end
|
273
|
+
|
274
|
+
def receive_data( data )
|
275
|
+
# puts "Data: #{data.bytes.map(&:chr)}"
|
276
|
+
@data << data
|
277
|
+
while @data.size >= MILTER_LEN_BYTES
|
278
|
+
pkt_len = @data[0...MILTER_LEN_BYTES].unpack('N').first
|
279
|
+
if @data.size >= MILTER_LEN_BYTES + pkt_len
|
280
|
+
@data.slice!(0, MILTER_LEN_BYTES)
|
281
|
+
pkt = @data.slice!(0, pkt_len)
|
282
|
+
cmd, val = pkt[0].chr, pkt[1..-1]
|
283
|
+
|
284
|
+
if COMMANDS.include?(cmd) and @milter.respond_to?(COMMANDS[cmd])
|
285
|
+
method_name = COMMANDS[cmd]
|
286
|
+
args = []
|
287
|
+
args = self.send('parse_' + method_name.to_s, val ) if self.respond_to?('parse_' + method_name.to_s )
|
288
|
+
ret = @milter.send(method_name, *args )
|
289
|
+
|
290
|
+
close_connection and return if cmd == SMFIC_QUIT
|
291
|
+
next if cmd == SMFIC_MACRO
|
292
|
+
|
293
|
+
if not ret.is_a? Array
|
294
|
+
ret = [ ret ]
|
295
|
+
end
|
296
|
+
|
297
|
+
ret.each do |r|
|
298
|
+
send_milter_response(r)
|
299
|
+
end
|
300
|
+
else
|
301
|
+
next if cmd == SMFIC_MACRO
|
302
|
+
send_milter_response(RESPONSE[:continue])
|
303
|
+
end
|
304
|
+
else
|
305
|
+
break
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
class << self
|
311
|
+
def register( milter_class )
|
312
|
+
@@milter_class = milter_class
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class << self
|
318
|
+
def register( milter_class )
|
319
|
+
MilterConnectionHandler.register(milter_class)
|
320
|
+
end
|
321
|
+
|
322
|
+
def start( host = 'localhost', port = 8888 )
|
323
|
+
EM.run do
|
324
|
+
Signal.trap("INT") { EventMachine.stop }
|
325
|
+
Signal.trap("TERM") { EventMachine.stop }
|
326
|
+
EM.start_server host, port, MilterConnectionHandler
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
data/milter.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'milter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "milter"
|
8
|
+
spec.version = Milter::VERSION
|
9
|
+
spec.authors = ["Markus Strauss"]
|
10
|
+
spec.email = ["Markus@ITstrauss.eu"]
|
11
|
+
# spec.description = %q{A pure Ruby Milter library}
|
12
|
+
spec.summary = %q{A pure Ruby Milter library}
|
13
|
+
spec.homepage = "https://github.com/mstrauss/ruby-milter.git"
|
14
|
+
# spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_dependency "eventmachine", "~> 1.0"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: milter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Markus Strauss
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: eventmachine
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
description:
|
63
|
+
email:
|
64
|
+
- Markus@ITstrauss.eu
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- Gemfile.lock
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- examples/body_filter.rb
|
75
|
+
- examples/logging.rb
|
76
|
+
- lib/milter.rb
|
77
|
+
- lib/milter/version.rb
|
78
|
+
- milter.gemspec
|
79
|
+
homepage: https://github.com/mstrauss/ruby-milter.git
|
80
|
+
licenses: []
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ! '>='
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
hash: -2274297183430300128
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
hash: -2274297183430300128
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.8.25
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: A pure Ruby Milter library
|
109
|
+
test_files: []
|