uron 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/README.md +150 -0
- data/Rakefile +7 -0
- data/bin/uron.rb +309 -0
- data/test/smtpmock.rb +38 -0
- data/test/test_uron.rb +262 -0
- data/uron.gemspec +21 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0591180df0f68252415b03b5a89b7d05126f8916
|
4
|
+
data.tar.gz: c71803232c8244075cdad60c3b888ff06bbd1ad6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 443387254be0ecaf8dca82886036822369b5c266b9ea642bdb3098cafc32b3afaf33db3f2878a7cddee814eb339816c98e24ab81039d5112ea9ac6b2444bdcf2
|
7
|
+
data.tar.gz: 1ce8c88b1106bf918c36e2050dddd20195f94054111f50783d9ba42a5db791032143e86b606ea92a6c4e8cfd6cd5047c756001adb0c11deb0df2683a7d37f5a7
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
[](https://travis-ci.org/unak/uron)
|
2
|
+
|
3
|
+
uron
|
4
|
+
====
|
5
|
+
|
6
|
+
This software is published at `https://github.com/unak/uron`.
|
7
|
+
|
8
|
+
|
9
|
+
What's This?
|
10
|
+
------------
|
11
|
+
|
12
|
+
uron is a mail delivery agent, like procmail.
|
13
|
+
|
14
|
+
Currently, uron has not been tested well yet.
|
15
|
+
|
16
|
+
|
17
|
+
Requirement
|
18
|
+
-----------
|
19
|
+
|
20
|
+
Ruby 1.8.7, 1.9.3 or later.
|
21
|
+
|
22
|
+
uron assumes that your system follows the Maildir specification.
|
23
|
+
|
24
|
+
|
25
|
+
How to Use
|
26
|
+
----------
|
27
|
+
|
28
|
+
Write `~/.forward` file.
|
29
|
+
If you cannot understand what you have to do, you cannot use uron.
|
30
|
+
|
31
|
+
|
32
|
+
Delivery Setting
|
33
|
+
----------------
|
34
|
+
Write `~/.uronrc` file.
|
35
|
+
|
36
|
+
### Basic Definitions
|
37
|
+
|
38
|
+
#### `Maildir`
|
39
|
+
|
40
|
+
The path of your maildir.
|
41
|
+
If not specified, `~/Maildir` is assumed.
|
42
|
+
|
43
|
+
#### `Log`
|
44
|
+
|
45
|
+
The path of the log file.
|
46
|
+
If not specified, uron outputs no log.
|
47
|
+
|
48
|
+
### Delivery Rules
|
49
|
+
|
50
|
+
In delivery rules, you can write Ruby code in code blocks.
|
51
|
+
If a block returns a true value, uron assumes that the mail is delivered
|
52
|
+
and exits.
|
53
|
+
|
54
|
+
#### `header`
|
55
|
+
|
56
|
+
Takes one Hash parameter and optional block.
|
57
|
+
|
58
|
+
The Hash parameter must include at least one key which means a mail header.
|
59
|
+
The key is all lower cases and converted `-` to `_'.
|
60
|
+
The value of the key must be a Regexp or an Array of Regexps.
|
61
|
+
uron matches the Regexp(s) to the value of mail headers specified by the key,
|
62
|
+
and do something if matched.
|
63
|
+
|
64
|
+
If `:delivery` is included in the Hash parameter, delivery the mail to
|
65
|
+
the value, and exits.
|
66
|
+
If `:transfer` is included in the Hash parameter, transfer the mail to
|
67
|
+
the value, and exits.
|
68
|
+
If `:invoke` is included in the Hash parameter, invoke the command specfied
|
69
|
+
by the value, and if the command returns zero, exits.
|
70
|
+
If a block is passed, call the block.
|
71
|
+
|
72
|
+
Examples:
|
73
|
+
|
74
|
+
header :subject => /\A[mailing-list:/, :delivery => "mailing-list"
|
75
|
+
This means that if the subject of the mail starts with `[mailing-list:`,
|
76
|
+
delivery the mail to `mailing-list` directory (it's relative from your
|
77
|
+
Maildir.)
|
78
|
+
|
79
|
+
header :subject => /\A[mailing-list:/ do
|
80
|
+
delivery "mailing-list"
|
81
|
+
end
|
82
|
+
Same as above.
|
83
|
+
|
84
|
+
### Delivery Commands
|
85
|
+
|
86
|
+
#### `delivery`
|
87
|
+
|
88
|
+
Takes one String parameter and delivery the mail to the directory specfied by
|
89
|
+
the parameter.
|
90
|
+
The parameter must be relative from your Maildir.
|
91
|
+
|
92
|
+
Examples:
|
93
|
+
|
94
|
+
delivery "mailing-list"
|
95
|
+
|
96
|
+
#### `transfer`
|
97
|
+
|
98
|
+
Takes two String parameters and transfer the mail to the host and the address
|
99
|
+
specfied by the parameters.
|
100
|
+
|
101
|
+
Examples:
|
102
|
+
|
103
|
+
transfer "some.host.of.example.com", "foo@example.com"
|
104
|
+
|
105
|
+
#### `invoke`
|
106
|
+
|
107
|
+
Takes at least one String parameters and invoke the command specified by
|
108
|
+
the 1st parameter and passes command arguments specified by rest parameters.
|
109
|
+
Passes the mail via stdin to the command.
|
110
|
+
|
111
|
+
Returns the status value of the command.
|
112
|
+
|
113
|
+
Examples:
|
114
|
+
|
115
|
+
if invoke("bsfilter", "-a") == 0
|
116
|
+
delivery ".spam"
|
117
|
+
else
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
#### `logging`
|
122
|
+
|
123
|
+
Takes one String parameter and outout it to the log file.
|
124
|
+
|
125
|
+
|
126
|
+
License
|
127
|
+
-------
|
128
|
+
|
129
|
+
Copyright (c) 2012 NAKAMURA Usaku usa@garbagecollect.jp
|
130
|
+
|
131
|
+
Redistribution and use in source and binary forms, with or without
|
132
|
+
modification, are permitted provided that the following conditions are met:
|
133
|
+
|
134
|
+
1. Redistributions of source code must retain the above copyright notice,
|
135
|
+
this list of conditions and the following disclaimer.
|
136
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
137
|
+
this list of conditions and the following disclaimer in the documentation
|
138
|
+
and/or other materials provided with the distribution.
|
139
|
+
|
140
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
|
141
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
142
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
143
|
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
|
144
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
145
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
146
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
147
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
148
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
149
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
150
|
+
|
data/Rakefile
ADDED
data/bin/uron.rb
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
#!ruby
|
2
|
+
# coding: UTF-8
|
3
|
+
|
4
|
+
#
|
5
|
+
# Copyright (c) 2012 NAKAMURA Usaku usa@garbagecollect.jp
|
6
|
+
#
|
7
|
+
# Redistribution and use in source and binary forms, with or without
|
8
|
+
# modification, are permitted provided that the following conditions are met:
|
9
|
+
#
|
10
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer.
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
13
|
+
# this list of conditions and the following disclaimer in the documentation
|
14
|
+
# and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
|
17
|
+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
|
20
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
21
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
22
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
23
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
25
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
26
|
+
#
|
27
|
+
|
28
|
+
require "etc"
|
29
|
+
require "fileutils"
|
30
|
+
require "net/smtp"
|
31
|
+
require "socket"
|
32
|
+
|
33
|
+
#
|
34
|
+
#= uron - a mail delivery agent
|
35
|
+
#
|
36
|
+
class Uron
|
37
|
+
ConfigError = Class.new(RuntimeError)
|
38
|
+
|
39
|
+
# execute uron
|
40
|
+
#
|
41
|
+
# _rc_ is a String of the configuration file.
|
42
|
+
# _io_ is a IO of the mail. (optional)
|
43
|
+
def self.run(rc, io = $stdin)
|
44
|
+
uron = Uron.new(rc)
|
45
|
+
uron.run(io)
|
46
|
+
end
|
47
|
+
|
48
|
+
# processed mail
|
49
|
+
attr_reader :mail
|
50
|
+
|
51
|
+
# path of Maildir
|
52
|
+
attr_reader :maildir
|
53
|
+
|
54
|
+
# path of log file
|
55
|
+
attr_reader :logfile
|
56
|
+
|
57
|
+
# initialize the Uron object
|
58
|
+
#
|
59
|
+
# _rc_ is a String of the configuration file.
|
60
|
+
# _io_ is a IO of the mail.
|
61
|
+
def initialize(rc)
|
62
|
+
self.class.class_eval do
|
63
|
+
remove_const :Maildir if defined?(Maildir)
|
64
|
+
remove_const :Log if defined?(Log)
|
65
|
+
end
|
66
|
+
|
67
|
+
@ruleset = []
|
68
|
+
|
69
|
+
open(rc) do |f|
|
70
|
+
eval(f.read, binding, rc)
|
71
|
+
end
|
72
|
+
|
73
|
+
@maildir = File.expand_path((Maildir rescue "~/Maildir"))
|
74
|
+
@logfile = File.expand_path(Log) rescue nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# execute uron
|
78
|
+
#
|
79
|
+
# _io_ is a IO of the mail. (optional)
|
80
|
+
def run(io = $stdin)
|
81
|
+
@mail = self.class::Mail.read(io)
|
82
|
+
|
83
|
+
logging "From #{(@mail.headers[:from] || []).first} #{Time.now}"
|
84
|
+
logging " Subject: #{@mail.headers[:subject].first[0, 69]}" if @mail.headers.include?(:subject)
|
85
|
+
|
86
|
+
catch(:tag) do
|
87
|
+
@ruleset.each do |sym, conds, block|
|
88
|
+
if @mail.headers[sym]
|
89
|
+
conds = [conds] unless conds.is_a?(Array)
|
90
|
+
conds.each do |cond|
|
91
|
+
@mail.headers[sym].each do |header|
|
92
|
+
begin
|
93
|
+
block.call(@mail) && throw(:tag) if cond =~ header
|
94
|
+
rescue
|
95
|
+
logging $!
|
96
|
+
raise $!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# if here, no rule was adpoted
|
104
|
+
delivery ""
|
105
|
+
end
|
106
|
+
|
107
|
+
0
|
108
|
+
end
|
109
|
+
|
110
|
+
# output a log
|
111
|
+
#
|
112
|
+
# _log_ is an Exception or a String.
|
113
|
+
def logging(log)
|
114
|
+
return unless @logfile
|
115
|
+
open(@logfile, "a") do |f|
|
116
|
+
f.flock(File::LOCK_EX)
|
117
|
+
f.seek(0, File::SEEK_END)
|
118
|
+
|
119
|
+
if log.is_a?(Exception)
|
120
|
+
log = ["#{log.class}: #{log.message}", *log.backtrace].join("\n\t")
|
121
|
+
end
|
122
|
+
f.puts log
|
123
|
+
|
124
|
+
f.flock(File::LOCK_UN)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# check specified header and process the mail
|
129
|
+
#
|
130
|
+
# _h_ is a Hash which includes a Symbol of a header as the key and an Array
|
131
|
+
# of Regexps as the value to checking the contents of the header.
|
132
|
+
# if _h_ includes :dir, the value means the path to be delivered.
|
133
|
+
# if _block_ is passed, uron processes it.
|
134
|
+
def header(h, &block)
|
135
|
+
deliv = h.delete(:delivery)
|
136
|
+
trans = h.delete(:transfer)
|
137
|
+
invok = h.delete(:invoke)
|
138
|
+
t = [deliv, trans, invok, block].compact
|
139
|
+
raise ConfigError, "need one of :delivery, :transfer, :invoke or a block" if t.empty?
|
140
|
+
raise ConfigError, "can specify only one of :delivery, :transfer, :invoke or a block" if t.size != 1
|
141
|
+
block = proc{ delivery deliv } if deliv
|
142
|
+
block = proc{ transfer *trans } if trans
|
143
|
+
block = proc{ invoke(*invok) == 0 } if invok
|
144
|
+
@ruleset.push([h.keys.first, h.values.flatten(1), block])
|
145
|
+
end
|
146
|
+
|
147
|
+
# deliver the mail to a directory
|
148
|
+
#
|
149
|
+
# _dir_ is a String specifes the target directory.
|
150
|
+
def delivery(dir)
|
151
|
+
dir = @maildir if dir.empty?
|
152
|
+
ldir = File.expand_path(File.join(dir, "new"), @maildir)
|
153
|
+
FileUtils.mkdir_p(ldir)
|
154
|
+
n = 1
|
155
|
+
begin
|
156
|
+
file = "%d.%d_%d.%s" % [Time.now.to_i, Process.pid, n, Socket.gethostname]
|
157
|
+
open(File.expand_path(file, ldir), "wb", File::CREAT | File::EXCL) do |f|
|
158
|
+
f.write mail.plain
|
159
|
+
f.chmod 0600
|
160
|
+
end
|
161
|
+
logging " Folder: %.60s %8d" % [File.join(dir, 'new', file)[0, 60], mail.plain.bytesize]
|
162
|
+
rescue Errno::EACCES
|
163
|
+
if n > 100
|
164
|
+
logging $!
|
165
|
+
raise $!
|
166
|
+
end
|
167
|
+
n += 1
|
168
|
+
retry
|
169
|
+
end
|
170
|
+
true # means success
|
171
|
+
end
|
172
|
+
|
173
|
+
# transfer the mail to some host
|
174
|
+
#
|
175
|
+
# _host_ is a String specifies the target host name (or the IP address).
|
176
|
+
# _to_ is a String specifies the target address.
|
177
|
+
# _port_ is an optional parameter of a Numeric specifies the target host port.
|
178
|
+
# _from_ is an optional parameter of a String specifies the envelove from.
|
179
|
+
def transfer(host, to, port = 25, from = nil)
|
180
|
+
from ||= Etc.getlogin
|
181
|
+
Net::SMTP.start(host, port) do |smtp|
|
182
|
+
smtp.send_mail(mail.plain, from, to)
|
183
|
+
end
|
184
|
+
logging " Trans: %.60s %8d" % [to[0, 60], mail.plain.bytesize]
|
185
|
+
true # mains success
|
186
|
+
end
|
187
|
+
|
188
|
+
# invoke a command
|
189
|
+
#
|
190
|
+
# _cmd_ is a String specifies the command.
|
191
|
+
# _args_ are Strings that will be passed to the command.
|
192
|
+
#
|
193
|
+
# this method passes the mail to the command via stdin, and returns the exit
|
194
|
+
# status value of it.
|
195
|
+
def invoke(cmd, *args)
|
196
|
+
result = nil
|
197
|
+
begin
|
198
|
+
unless args.empty?
|
199
|
+
cmd = cmd + ' ' + args.map{|e| "'#{e}'"}.join(' ')
|
200
|
+
end
|
201
|
+
IO.popen(cmd, 'wb') do |f|
|
202
|
+
f.print mail.plain
|
203
|
+
end
|
204
|
+
result = $?.to_i
|
205
|
+
rescue
|
206
|
+
result = -1
|
207
|
+
logging $!
|
208
|
+
raise $!
|
209
|
+
end
|
210
|
+
logging " Invoke: %.60s %8d" % [cmd[0, 60], result]
|
211
|
+
result
|
212
|
+
end
|
213
|
+
|
214
|
+
# mail
|
215
|
+
class Mail
|
216
|
+
# read a mail from stdin
|
217
|
+
#
|
218
|
+
# _io_ is a IO of the mail. (optional)
|
219
|
+
def self.read(io = $stdin)
|
220
|
+
self.new(io.binmode.read)
|
221
|
+
end
|
222
|
+
|
223
|
+
# a Hash of mail headers
|
224
|
+
attr_reader :headers
|
225
|
+
|
226
|
+
# an Array of mail body
|
227
|
+
attr_reader :body
|
228
|
+
|
229
|
+
# a String of the orignal mail text
|
230
|
+
attr_reader :plain
|
231
|
+
|
232
|
+
# initialize a Uron::Mail object
|
233
|
+
#
|
234
|
+
# _plain_ is the original mail text.
|
235
|
+
def initialize(plain)
|
236
|
+
@plain = plain.dup
|
237
|
+
parse(@plain)
|
238
|
+
end
|
239
|
+
|
240
|
+
# parse the mail text
|
241
|
+
#
|
242
|
+
# _plain_ is the original mail text.
|
243
|
+
def parse(plain)
|
244
|
+
@headers = {}
|
245
|
+
@body = []
|
246
|
+
header_p = true
|
247
|
+
prev = nil
|
248
|
+
plain.each_line do |line|
|
249
|
+
if header_p
|
250
|
+
# header
|
251
|
+
line.chomp!
|
252
|
+
if line.empty?
|
253
|
+
set_header(prev) if prev
|
254
|
+
header_p = false
|
255
|
+
next
|
256
|
+
end
|
257
|
+
|
258
|
+
if /\A\s/ =~ line
|
259
|
+
prev = (prev || "") + " " + line.sub(/\A\s+/, '')
|
260
|
+
else
|
261
|
+
set_header(prev) if prev
|
262
|
+
prev = line
|
263
|
+
end
|
264
|
+
else
|
265
|
+
# body
|
266
|
+
if @body.empty?
|
267
|
+
@body.push(line)
|
268
|
+
else
|
269
|
+
@body.last << line
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# set a header to @headers
|
276
|
+
def set_header(line)
|
277
|
+
title, data = line.split(/: */, 2)
|
278
|
+
title = title.tr("-", "_").downcase.to_sym
|
279
|
+
@headers[title] = [] unless @headers.include?(title)
|
280
|
+
@headers[title].push(data)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
if __FILE__ == $0
|
286
|
+
require "optparse"
|
287
|
+
|
288
|
+
rcfile = "~/.uronrc"
|
289
|
+
|
290
|
+
opt = OptionParser.new
|
291
|
+
opt.on('-r RCFILE', '--rc', 'use RCFILE as the ruleset configurations.') do |v|
|
292
|
+
rcfile = v
|
293
|
+
end
|
294
|
+
opt.parse!
|
295
|
+
unless ARGV.empty?
|
296
|
+
$stderr.puts "unknown argument(s): #{ARGV.join(' ')}"
|
297
|
+
$stderr.puts
|
298
|
+
$stderr.puts opt.help
|
299
|
+
exit 1
|
300
|
+
end
|
301
|
+
|
302
|
+
begin
|
303
|
+
exit Uron.run(File.expand_path(rcfile))
|
304
|
+
rescue
|
305
|
+
$stderr.puts "#{$!.class}: #{$!.message}"
|
306
|
+
$stderr.puts $!.backtrace.join("\n\t")
|
307
|
+
exit 1
|
308
|
+
end
|
309
|
+
end
|
data/test/smtpmock.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "net/smtp"
|
2
|
+
|
3
|
+
class SMTPMock
|
4
|
+
attr_reader :host
|
5
|
+
attr_reader :port
|
6
|
+
attr_reader :helo
|
7
|
+
attr_reader :src
|
8
|
+
attr_reader :from
|
9
|
+
attr_reader :to
|
10
|
+
|
11
|
+
def self.instance
|
12
|
+
@@instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(host, port = 25)
|
16
|
+
@@instance = self
|
17
|
+
|
18
|
+
@host = host
|
19
|
+
@port = port
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(helo, account, password, auth, &block)
|
23
|
+
@helo = helo
|
24
|
+
block.call(self) if block
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_mail(src, from, *to)
|
28
|
+
@src = src
|
29
|
+
@from = from
|
30
|
+
@to = to
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Net::SMTP
|
35
|
+
def self.new(*args)
|
36
|
+
SMTPMock.new(*args)
|
37
|
+
end
|
38
|
+
end
|
data/test/test_uron.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "etc"
|
3
|
+
require "fileutils"
|
4
|
+
require "rbconfig"
|
5
|
+
require "stringio"
|
6
|
+
require "tempfile"
|
7
|
+
require "tmpdir"
|
8
|
+
unless defined?(require_relative)
|
9
|
+
def require_relative(feature)
|
10
|
+
require File.expand_path(feature, File.dirname(File.expand_path(__FILE__)))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
require_relative "smtpmock"
|
14
|
+
require_relative "../bin/uron"
|
15
|
+
|
16
|
+
unless defined?(File::NULL)
|
17
|
+
if /mswin|mingw|bccwin|djgpp/ =~ RUBY_PLATFORM
|
18
|
+
File::NULL = "NUL"
|
19
|
+
else
|
20
|
+
File::NULL = "/dev/null"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class TestUron < Test::Unit::TestCase
|
26
|
+
def setup
|
27
|
+
@tmpdir = Dir.mktmpdir
|
28
|
+
@maildir = @tmpdir
|
29
|
+
@logfile = File.expand_path("log", @maildir)
|
30
|
+
|
31
|
+
ruby = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"])
|
32
|
+
@rc = make_rc <<-END_OF_RC
|
33
|
+
Maildir = "#{@tmpdir}"
|
34
|
+
Log = "#{@logfile}"
|
35
|
+
|
36
|
+
header :from => /\\Ausa@/ do
|
37
|
+
delivery ".test"
|
38
|
+
end
|
39
|
+
|
40
|
+
header :to => /\\Ausa@/, :delivery => ".test"
|
41
|
+
|
42
|
+
header :from => /\\Ausa2@/ do
|
43
|
+
transfer "mx.example.com", "usa@example.com"
|
44
|
+
end
|
45
|
+
|
46
|
+
header :to => /\\Ausa2@/, :transfer => ["mx.example.com", "usa@example.com"]
|
47
|
+
|
48
|
+
header :from => /\\Ausa3@/ do
|
49
|
+
invoke("#{ruby}", "-e", "exit /^From:.*usa3@/ =~ ARGF.read ? 0 : 1") == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
header :to => /\\Ausa3@/, :invoke => ["#{ruby}", "-e", "exit /^To:.*usa3@/ =~ ARGF.read ? 0 : 1"]
|
53
|
+
END_OF_RC
|
54
|
+
end
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
@rc.unlink
|
58
|
+
FileUtils.rm_rf @tmpdir
|
59
|
+
end
|
60
|
+
|
61
|
+
def make_rc(str)
|
62
|
+
tmprc = Tempfile.open("uron_test")
|
63
|
+
tmprc.binmode
|
64
|
+
tmprc.puts str
|
65
|
+
tmprc.close
|
66
|
+
tmprc
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_new
|
70
|
+
uron = Uron.new(@rc.path)
|
71
|
+
assert uron.is_a?(Uron)
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_run_m
|
75
|
+
null = open(File::NULL)
|
76
|
+
begin
|
77
|
+
assert_nothing_raised do
|
78
|
+
Uron.run(@rc.path, null)
|
79
|
+
end
|
80
|
+
ensure
|
81
|
+
null.close
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_run
|
86
|
+
uron = Uron.new(@rc.path)
|
87
|
+
null = open(File::NULL)
|
88
|
+
begin
|
89
|
+
assert_nothing_raised do
|
90
|
+
uron.run(null)
|
91
|
+
end
|
92
|
+
ensure
|
93
|
+
null.close
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_maildir
|
98
|
+
uron = Uron.new(@rc.path)
|
99
|
+
assert_equal @maildir, uron.maildir
|
100
|
+
|
101
|
+
uron = Uron.new(File::NULL)
|
102
|
+
assert_equal File.expand_path("~/Maildir"), uron.maildir
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_logfile
|
106
|
+
uron = Uron.new(@rc.path)
|
107
|
+
assert_equal @logfile, uron.logfile
|
108
|
+
|
109
|
+
uron = Uron.new(File::NULL)
|
110
|
+
assert_nil uron.logfile
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_logging
|
114
|
+
uron = Uron.new(@rc.path)
|
115
|
+
uron.logging "test"
|
116
|
+
assert_equal "test", File.read(uron.logfile).chomp
|
117
|
+
|
118
|
+
ex = nil
|
119
|
+
begin
|
120
|
+
raise RuntimeError, "foo"
|
121
|
+
rescue
|
122
|
+
ex = $!
|
123
|
+
end
|
124
|
+
uron.logging ex
|
125
|
+
assert_match /^RuntimeError: foo\n\t.*\btest_uron\.rb:\d+:in `test_logging'/, File.read(uron.logfile) #'
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_header
|
129
|
+
tmprc = make_rc <<-END_OF_RC
|
130
|
+
header :foo => //
|
131
|
+
END_OF_RC
|
132
|
+
assert_raise(Uron::ConfigError) do
|
133
|
+
Uron.new(tmprc.path)
|
134
|
+
end
|
135
|
+
tmprc.unlink
|
136
|
+
|
137
|
+
tmprc = make_rc <<-END_OF_RC
|
138
|
+
header :foo => //, :delivery => "", :transfer => []
|
139
|
+
END_OF_RC
|
140
|
+
assert_raise(Uron::ConfigError) do
|
141
|
+
Uron.new(tmprc.path)
|
142
|
+
end
|
143
|
+
tmprc.unlink
|
144
|
+
|
145
|
+
tmprc = make_rc <<-END_OF_RC
|
146
|
+
header :foo => //, :delivery => "" do
|
147
|
+
end
|
148
|
+
END_OF_RC
|
149
|
+
assert_raise(Uron::ConfigError) do
|
150
|
+
Uron.new(tmprc.path)
|
151
|
+
end
|
152
|
+
tmprc.unlink
|
153
|
+
|
154
|
+
tmprc = make_rc <<-END_OF_RC
|
155
|
+
header :foo => //, :transfer => [] do
|
156
|
+
end
|
157
|
+
END_OF_RC
|
158
|
+
assert_raise(Uron::ConfigError) do
|
159
|
+
Uron.new(tmprc.path)
|
160
|
+
end
|
161
|
+
tmprc.unlink
|
162
|
+
|
163
|
+
tmprc = make_rc <<-END_OF_RC
|
164
|
+
Log = "#{@logfile}"
|
165
|
+
header :from => /\\Ausa\\b/ do
|
166
|
+
next false
|
167
|
+
end
|
168
|
+
header :from => /\\Ausa\\b/ do
|
169
|
+
next true
|
170
|
+
end
|
171
|
+
END_OF_RC
|
172
|
+
assert_nothing_raised do
|
173
|
+
io = StringIO.new("From: usa@example.com\r\n\r\n")
|
174
|
+
assert_equal 0, Uron.run(tmprc.path, io)
|
175
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
176
|
+
assert_nil mail
|
177
|
+
assert_match /\AFrom [^\n]+\r?\n\z/, File.read(@logfile)
|
178
|
+
end
|
179
|
+
tmprc.unlink
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_delivery
|
183
|
+
io = StringIO.new("From: usa@example.com\r\n\r\n")
|
184
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
185
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
186
|
+
assert_nil mail
|
187
|
+
mail = Dir.glob(File.join(@maildir, ".test", "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
188
|
+
assert_equal io.string, open(mail, "rb"){|f| f.read}
|
189
|
+
assert_match /\sFolder:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
190
|
+
File.unlink mail if File.exist?(mail)
|
191
|
+
|
192
|
+
io = StringIO.new("To: usa@example.com\r\n\r\n")
|
193
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
194
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
195
|
+
assert_nil mail
|
196
|
+
mail = Dir.glob(File.join(@maildir, ".test", "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
197
|
+
assert_equal io.string, open(mail, "rb"){|f| f.read}
|
198
|
+
assert_match /\sFolder:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
199
|
+
File.unlink mail if File.exist?(mail)
|
200
|
+
|
201
|
+
io = StringIO.new("From: foo@example.com\r\n\r\n")
|
202
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
203
|
+
mail = Dir.glob(File.join(@maildir, ".test", "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
204
|
+
assert_nil mail
|
205
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
206
|
+
io.rewind
|
207
|
+
assert_equal io.string, open(mail, "rb"){|f| f.read}
|
208
|
+
assert_match /\sFolder:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_transfer
|
212
|
+
io = StringIO.new("From: usa2@example.com\r\n\r\n")
|
213
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
214
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
215
|
+
assert_nil mail
|
216
|
+
assert_match /\sTrans:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
217
|
+
assert_equal "mx.example.com", SMTPMock.instance.host
|
218
|
+
assert_equal 25, SMTPMock.instance.port
|
219
|
+
assert_match /\Alocalhost\b/, SMTPMock.instance.helo
|
220
|
+
assert_equal Etc.getlogin, SMTPMock.instance.from
|
221
|
+
assert_equal ["usa@example.com"], SMTPMock.instance.to
|
222
|
+
assert_equal io.string, SMTPMock.instance.src
|
223
|
+
|
224
|
+
io = StringIO.new("To: usa2@example.com\r\n\r\n")
|
225
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
226
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
227
|
+
assert_nil mail
|
228
|
+
assert_match /\sTrans:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
229
|
+
assert_equal "mx.example.com", SMTPMock.instance.host
|
230
|
+
assert_equal 25, SMTPMock.instance.port
|
231
|
+
assert_match /\Alocalhost\b/, SMTPMock.instance.helo
|
232
|
+
assert_equal Etc.getlogin, SMTPMock.instance.from
|
233
|
+
assert_equal ["usa@example.com"], SMTPMock.instance.to
|
234
|
+
assert_equal io.string, SMTPMock.instance.src
|
235
|
+
|
236
|
+
io = StringIO.new(s = "From: foo@example.com\r\n\r\n")
|
237
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
238
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
239
|
+
assert_equal io.string, open(mail, "rb"){|f| f.read}
|
240
|
+
assert_match /\sFolder:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_invoke
|
244
|
+
io = StringIO.new("From: usa3@example.com\r\n\r\n")
|
245
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
246
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
247
|
+
assert_nil mail
|
248
|
+
assert_match /\sInvoke:[^\n]+\s0\r?\n\z/, File.read(@logfile)
|
249
|
+
|
250
|
+
io = StringIO.new("To: usa3@example.com\r\n\r\n")
|
251
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
252
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
253
|
+
assert_nil mail
|
254
|
+
assert_match /\sInvoke:[^\n]+\s0\r?\n\z/, File.read(@logfile)
|
255
|
+
|
256
|
+
io = StringIO.new(s = "From: foo@example.com\r\n\r\n")
|
257
|
+
assert_equal 0, Uron.run(@rc.path, io)
|
258
|
+
mail = Dir.glob(File.join(@maildir, "new", "*")).find{|e| /\A[^\.]/ =~ e}
|
259
|
+
assert_equal io.string, open(mail, "rb"){|f| f.read}
|
260
|
+
assert_match /\sFolder:[^\n]+\s#{io.string.size}\r?\n\z/, File.read(@logfile)
|
261
|
+
end
|
262
|
+
end
|
data/uron.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# -*- Ruby -*-
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "uron"
|
5
|
+
spec.version = "1.0.0"
|
6
|
+
spec.authors = ["U.Nakamura"]
|
7
|
+
spec.email = ["usa@garbagecollect.jp"]
|
8
|
+
spec.description = %q{uron is a mail delivery agent}
|
9
|
+
spec.summary = %q{uron is a mail delivery agent}
|
10
|
+
spec.homepage = "https://github.com/unak/uron"
|
11
|
+
spec.license = "BSD-2-Clause"
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split($/)
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
19
|
+
spec.add_development_dependency "rake"
|
20
|
+
spec.add_development_dependency "test-unit"
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uron
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- U.Nakamura
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: uron is a mail delivery agent
|
56
|
+
email:
|
57
|
+
- usa@garbagecollect.jp
|
58
|
+
executables:
|
59
|
+
- uron.rb
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/uron.rb
|
69
|
+
- test/smtpmock.rb
|
70
|
+
- test/test_uron.rb
|
71
|
+
- uron.gemspec
|
72
|
+
homepage: https://github.com/unak/uron
|
73
|
+
licenses:
|
74
|
+
- BSD-2-Clause
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.2.2
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: uron is a mail delivery agent
|
96
|
+
test_files:
|
97
|
+
- test/smtpmock.rb
|
98
|
+
- test/test_uron.rb
|