milter 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|