uron 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://img.shields.io/travis/unak/uron.svg)](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
|