lmtp 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.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/README.rdoc +97 -0
- data/lib/lmtp.rb +380 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 152778817318a42ad2aec86517d416b945e51fae
|
4
|
+
data.tar.gz: 4bc49d66edc5055c69681bc4d1079b62dababd9f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 40fee91812ede656b71e820d3b74fff8a013e2068ad72f130a2c51b06daf27ffa680c20212ab1b63b830f2a7483bab1ea5688be6f1ad82ce4bb86ac1d098d4c1
|
7
|
+
data.tar.gz: 6bf889d743d0981615b7769a4184c1eea452d34c246d9c394b99e1c7663027c58327c47c973da9b05770c1ed0941f0852f7efedc81ee49a72d11ca82b7f9650d
|
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright © 2015 Marvin Gülker
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are
|
6
|
+
met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
12
|
+
notice, this list of conditions and the following disclaimer in the
|
13
|
+
documentation and/or other materials provided with the distribution.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
16
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
17
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
18
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
19
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
20
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
21
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
22
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
23
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
= Minimal LMTP Server for Ruby
|
2
|
+
|
3
|
+
This project is a minimal LMTP server for Ruby. Ever wanted to stick
|
4
|
+
your Ruby application behind a real MTA like Postfix so it can receive
|
5
|
+
mails directly? This library is what you need.
|
6
|
+
|
7
|
+
This library provides a single class, LmtpServer, which will open a
|
8
|
+
UNIX domain socket and listen for LMTP commands on it. You then direct
|
9
|
+
your MTA to deliver the mails it receives via LMTP over the socket
|
10
|
+
opened by this library. The email (and surroundings) will then show up
|
11
|
+
in form of callbacks in your application. To speak in terms of email
|
12
|
+
administration, this library thus implements an MDA (mail delivery
|
13
|
+
agent).
|
14
|
+
|
15
|
+
It does not provide a means to actually process the emails you
|
16
|
+
receive, you will receive them as a large string with all the original
|
17
|
+
CRLF line separators included. It is recommended to use a library like
|
18
|
+
mail[https://github.com/mikel/mail] for that.
|
19
|
+
|
20
|
+
== Dependencies
|
21
|
+
|
22
|
+
None but Ruby’s standard +socket+ library.
|
23
|
+
|
24
|
+
== Usage
|
25
|
+
|
26
|
+
require "lmtp"
|
27
|
+
|
28
|
+
LmtpServer.new("/var/spool/postfix/private/ruby-lmtp") do |msg|
|
29
|
+
puts "-- Start email --"
|
30
|
+
puts msg
|
31
|
+
puts "-- End email --"
|
32
|
+
end
|
33
|
+
|
34
|
+
You can find a lot more examples in the documentation of the
|
35
|
+
LmtpServer class.
|
36
|
+
|
37
|
+
== Security considerations
|
38
|
+
|
39
|
+
LMTP (RFC 2033) was not designed to be used over the WWW and the RFC
|
40
|
+
actively recommends against such usage. LMTP contains no
|
41
|
+
authentication and similar security measures; it is only intended to
|
42
|
+
be used in a local, trusted environment, which is the reason why this
|
43
|
+
library only provides UNIX domain sockets for listening and not TCP
|
44
|
+
sockets.
|
45
|
+
|
46
|
+
Also keep in mind that this library is fairly minimal. While I think I
|
47
|
+
have implemented RFC 2033 correctly, I have not intensly read RFC 821
|
48
|
+
(SMTP), which is referenced from the LMTP specification at certain
|
49
|
+
parts. If unusual commands are used over the UNIX socket, unexpected
|
50
|
+
things may happen in form of exceptions. Feel free to report such
|
51
|
+
behaviour as a bug.
|
52
|
+
|
53
|
+
The library is not multithreaded. You can threadsafely call
|
54
|
+
LmtpServer#stop, but email messages will be proceeded one by one. If
|
55
|
+
multiple clients connect to the UNIX domain socket, they will be
|
56
|
+
queued and handled after one another. That is, if one client does not
|
57
|
+
quit the connection, the entire server is blocked until the
|
58
|
+
(configurable) timeout value is reached.
|
59
|
+
|
60
|
+
== Links
|
61
|
+
|
62
|
+
* Repository: https://github.com/Quintus/ruby-lmtp
|
63
|
+
* Bugtracker: https://github.com/Quintus/ruby-lmtp/issues
|
64
|
+
* The original Gist from which this project is a continuation:
|
65
|
+
https://gist.github.com/Quintus/f4faf26aa022f74f7778
|
66
|
+
|
67
|
+
While you are at it, check out my (German)
|
68
|
+
Blog[http://www.quintilianus.eu].
|
69
|
+
|
70
|
+
== License
|
71
|
+
|
72
|
+
Copyright © 2015 Marvin Gülker
|
73
|
+
|
74
|
+
All rights reserved.
|
75
|
+
|
76
|
+
Redistribution and use in source and binary forms, with or without
|
77
|
+
modification, are permitted provided that the following conditions are
|
78
|
+
met:
|
79
|
+
|
80
|
+
1. Redistributions of source code must retain the above copyright
|
81
|
+
notice, this list of conditions and the following disclaimer.
|
82
|
+
|
83
|
+
2. Redistributions in binary form must reproduce the above copyright
|
84
|
+
notice, this list of conditions and the following disclaimer in the
|
85
|
+
documentation and/or other materials provided with the distribution.
|
86
|
+
|
87
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
88
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
89
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
90
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
91
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
92
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
93
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
94
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
95
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
96
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
97
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/lib/lmtp.rb
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Copyright © 2015 Marvin Gülker
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# 1. Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
#
|
12
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer in the
|
14
|
+
# documentation and/or other materials provided with the distribution.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
20
|
+
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
23
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
24
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
25
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
|
28
|
+
require "socket"
|
29
|
+
|
30
|
+
# LMTP server class. Instances of this class utilize a UNIX socket to implement
|
31
|
+
# the LMTP protocol (see {RFC 2033}[https://tools.ietf.org/html/rfc1854]) in its
|
32
|
+
# most minimal and basic form. LMTP is spoken by possibly any MTA, so using this
|
33
|
+
# class you can make your Ruby program an email endpoint as long as you know how
|
34
|
+
# to configure your MTA. I’ve only tested with Postfix, though, so no guarantees.
|
35
|
+
#
|
36
|
+
# Instances of this class support several callbacks. The main callback is the message
|
37
|
+
# callback, which is passed the the block to ::new. It gets called whenever the
|
38
|
+
# LMTP client hands in an email, and receives the entire email as plain text
|
39
|
+
# as its argument. You can use the “mail” library or other means to parse it.
|
40
|
+
# Other callbacks you might find useful can be set with the #logging and #headers
|
41
|
+
# methods.
|
42
|
+
#
|
43
|
+
# This class makes no use of threads for multiple connections. Thus,
|
44
|
+
# any emails submitted at once to the UNIX domain socket are processed
|
45
|
+
# ony-by-one. A single client could block all other clients thus, but
|
46
|
+
# because LMTP should only ever be used in a completely trusted
|
47
|
+
# environment (see RFC 2033, sections 3 and 5), this is not an issue.
|
48
|
+
#
|
49
|
+
# It _does_ employ a mutex for the #stop method and the checking of the
|
50
|
+
# stopping variable. This means you can safely call #stop from another
|
51
|
+
# thread.
|
52
|
+
#
|
53
|
+
# Example use:
|
54
|
+
#
|
55
|
+
# server = LmtpServer.new("/var/spool/postfix/private/mysocket") do |message|
|
56
|
+
# puts "--- Start of email ---"
|
57
|
+
# puts message
|
58
|
+
# puts "--- End of email ---"
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# server.logging do |level, msg|
|
62
|
+
# $stderr.puts "[#{level}] #{msg}"
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# server.start
|
66
|
+
#
|
67
|
+
# The LMTP server by this class implements the following SMTP Service
|
68
|
+
# Extensions (see below for the list of RFCs). Do not implement them
|
69
|
+
# yourself by utilising #moreextensions and the callbacks, they’re
|
70
|
+
# there already!
|
71
|
+
#
|
72
|
+
# * PIPELINING
|
73
|
+
# * ENHANCEDSTATUSCODES
|
74
|
+
# * 8BITMIME
|
75
|
+
#
|
76
|
+
# RFCs implemented by this class:
|
77
|
+
#
|
78
|
+
# * {RFC 2033}[https://tools.ietf.org/html/rfc2033]
|
79
|
+
# * {RFC 2034}[https://tools.ietf.org/html/rfc2034]
|
80
|
+
# * {RFC 1854}[https://tools.ietf.org/html/rfc1854]
|
81
|
+
# * {RFC 1869}[https://tools.ietf.org/html/rfc1869]
|
82
|
+
# * {RFC 1652}[https://tools.ietf.org/html/rfc1652]
|
83
|
+
# * {RFC 821}[https://tools.ietf.org/html/rfc821] for the minimally required parts
|
84
|
+
class LmtpServer
|
85
|
+
|
86
|
+
# The machine’s hostname is read from this file.
|
87
|
+
HOSTNAME_FILE = "/etc/hostname".freeze
|
88
|
+
|
89
|
+
# Version of this library.
|
90
|
+
VERSION = "0.0.1".freeze
|
91
|
+
|
92
|
+
# Timeout in seconds when a client is forcibly disconnected when
|
93
|
+
# it does nothing.
|
94
|
+
attr_accessor :timeout
|
95
|
+
|
96
|
+
# This is an array of extra extensions that are announced to
|
97
|
+
# the client in response to LHLO. Just append the names of
|
98
|
+
# the extensions to this array (e.g. "MYCOOLEXTENSION").
|
99
|
+
# The class will take care to prefix is with the proper LMTP
|
100
|
+
# reply code.
|
101
|
+
#
|
102
|
+
# This array is empty by default. Modifying it only makes sense
|
103
|
+
# if you actually implement the extensions you advertise here.
|
104
|
+
attr_accessor :moreextensions
|
105
|
+
|
106
|
+
# Message text to return on a successful message acceptance. This is
|
107
|
+
# automatically prefixed by "250 2.6.0 " so you don’t have to care
|
108
|
+
# about the LMTP status code. This text is purely informational and
|
109
|
+
# has no meaning to the protocol. It will show up in the sending
|
110
|
+
# MTA’s logs.
|
111
|
+
attr_accessor :successmsg
|
112
|
+
|
113
|
+
# Create a new LMTP server.
|
114
|
+
#
|
115
|
+
# === Parameters
|
116
|
+
# [path]
|
117
|
+
# Path on which the UNIX domain socket is created.
|
118
|
+
# All parent directories must exist, but the “file” itself
|
119
|
+
# must not exist (an ArgumentError is thrown if it exists).
|
120
|
+
# [mode (nil)]
|
121
|
+
# UNIX permissions to set on the UNIX socket file as
|
122
|
+
# a numeric mode (example: 0666 for rw-rw-rw-).
|
123
|
+
# User and Group of the file are determined by whatever
|
124
|
+
# the process environment mandates. +nil+ means to use
|
125
|
+
# whatever the process umask mandates.
|
126
|
+
# [callback]
|
127
|
+
# Message callback. Receives any email as a string that is
|
128
|
+
# passed to this LMTP server. The string will contain the
|
129
|
+
# original carriagereturn+newline line breaks from the protcol.
|
130
|
+
#
|
131
|
+
# === Return value
|
132
|
+
# Returns the new instance.
|
133
|
+
def initialize(path, mode = nil, &callback)
|
134
|
+
@path = path
|
135
|
+
@mode = mode
|
136
|
+
@hostname = File.read(HOSTNAME_FILE).strip
|
137
|
+
@msgcb = callback
|
138
|
+
@client = nil
|
139
|
+
@timeout = 30
|
140
|
+
@mutex = Mutex.new
|
141
|
+
@do_stop = false
|
142
|
+
@headercb = method(:default_headercb)
|
143
|
+
@moreextensions = []
|
144
|
+
@successmsg = "All your bytes are belong to us."
|
145
|
+
|
146
|
+
if File.exist?(path)
|
147
|
+
raise(ArgumentError, "File already exists: #{path}")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Specify the logging callback. It will receive a syslog
|
152
|
+
# logging level as a symbol and the log message.
|
153
|
+
#
|
154
|
+
# By default, no logging callback is set and hence nothing
|
155
|
+
# is logged.
|
156
|
+
def logging(&callback)
|
157
|
+
@logcb = callback
|
158
|
+
end
|
159
|
+
|
160
|
+
# Override the callback used for responding to the LMTP client for
|
161
|
+
# the LMTP commands before DATA, e.g. MAIL FROM and RCPT TO. The
|
162
|
+
# callback receives the entire line the client sent, including the
|
163
|
+
# trailing carriagereturn-newline.
|
164
|
+
#
|
165
|
+
# The default callback only answers "250 2.1.0 ok" for every
|
166
|
+
# command. Note that RFC 2033 (LMTP) requires in section 5 that any
|
167
|
+
# LMTP server MUST implement RFC 2034, which in turn refers to RFC
|
168
|
+
# 1893 for the actual status codes, so for any replies you make you
|
169
|
+
# must make use of the extended statuscodes defined in RFC 1893 in
|
170
|
+
# the format defined by RFC 2034. Don’t worry — both of these RFCs
|
171
|
+
# are simple enough to just read quickly through them.
|
172
|
+
#
|
173
|
+
# Example:
|
174
|
+
#
|
175
|
+
# server.headers do |line|
|
176
|
+
# case line
|
177
|
+
# when /^MAIL FROM/ then "250 ok"
|
178
|
+
# when /^RCPT TO:<.*?>/ then
|
179
|
+
# if this_account_exists($1)
|
180
|
+
# "250 2.1.5 Recipient ok"
|
181
|
+
# else
|
182
|
+
# "550 5.1.1 Recipient does not exist over here."
|
183
|
+
# end
|
184
|
+
# else
|
185
|
+
# "250 2.1.0 ok"
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# Note that the replies you define here are not immediately sent
|
190
|
+
# to the client, which is a result of the PIPELINING extension
|
191
|
+
# that is required by LMTP (see RFC 2033, section 5, and RFC 1854).
|
192
|
+
# Instead they’re accumulated and send as a big swall to the client
|
193
|
+
# when he issues the DATA command.
|
194
|
+
def headers(&callback)
|
195
|
+
@headercb = callback
|
196
|
+
end
|
197
|
+
|
198
|
+
# Halt the running server. This method is threadsafe.
|
199
|
+
def stop
|
200
|
+
@mutex.synchronize{ @do_stop = true }
|
201
|
+
end
|
202
|
+
|
203
|
+
# Create the UNIX domain socket and start listening on it.
|
204
|
+
# This method starts a listening loop and thus blocks.
|
205
|
+
# Use #stop from another thread to issue a halt.
|
206
|
+
def start
|
207
|
+
log :info, "Starting server"
|
208
|
+
@mutex.synchronize{ @do_stop = false }
|
209
|
+
|
210
|
+
UNIXServer.open(@path) do |server|
|
211
|
+
File.chmod(@mode, @path) if @mode
|
212
|
+
|
213
|
+
log :info, "Accepting connections."
|
214
|
+
while @client = server.accept
|
215
|
+
addr = @client.addr.last
|
216
|
+
log :info, "Client connect from #{addr}."
|
217
|
+
|
218
|
+
# TODO: Use #accept_nonblock in loop? This way, a client has to connect
|
219
|
+
# first to have the server shut down.
|
220
|
+
break if @mutex.synchronize{ @do_stop }
|
221
|
+
|
222
|
+
begin
|
223
|
+
catch :timeout do
|
224
|
+
handle_client
|
225
|
+
end
|
226
|
+
rescue => e
|
227
|
+
log :err, "Exception: #{e.class.name}: #{e.message}: #{e.backtrace.join("\n")}"
|
228
|
+
log :err, "Aborting connection due to exception."
|
229
|
+
@client.close
|
230
|
+
end
|
231
|
+
|
232
|
+
log :info, "Client connection closed: #{addr}"
|
233
|
+
@client = nil
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
log :info, "Server stopped."
|
239
|
+
ensure
|
240
|
+
if File.exist?(@path)
|
241
|
+
log :info, "Removing UNIX socket '#@path'"
|
242
|
+
File.delete(@path)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def log(level, msg)
|
249
|
+
@logcb.call(level, msg) if @logcb
|
250
|
+
end
|
251
|
+
|
252
|
+
def reply(msg)
|
253
|
+
str = msg.strip + "\r\n"
|
254
|
+
log :debug, "server: #{str.inspect}"
|
255
|
+
@client.puts(str)
|
256
|
+
end
|
257
|
+
|
258
|
+
def gets(raw = false)
|
259
|
+
if IO.select([@client], nil, nil, @timeout)
|
260
|
+
str = @client.gets
|
261
|
+
log :debug, "client: #{str.inspect}"
|
262
|
+
return nil if str.nil?
|
263
|
+
|
264
|
+
str.gsub!("\r\n", "\n") unless raw
|
265
|
+
|
266
|
+
if str.strip == "RSET" && !raw # Ensure that in DATA we can ignore it if this text occurs
|
267
|
+
reply "220 2.0.0 Resetting."
|
268
|
+
throw :rset
|
269
|
+
end
|
270
|
+
|
271
|
+
str
|
272
|
+
else
|
273
|
+
log :err, "Client #{@client.addr.last} timed out. Closing."
|
274
|
+
reply "422 4.5.0 Timeout."
|
275
|
+
@client.close
|
276
|
+
throw :timeout
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def handle_client
|
281
|
+
reply "220 #{@hostname} LMTP server ready"
|
282
|
+
|
283
|
+
line = gets
|
284
|
+
if line !~ /^LHLO (.*?)$/
|
285
|
+
reply "500 5.5.1 You must great me first."
|
286
|
+
@client.close
|
287
|
+
return
|
288
|
+
end
|
289
|
+
|
290
|
+
log :info, "Client reports name: '#$1'"
|
291
|
+
|
292
|
+
reply "250-#{@hostname}"
|
293
|
+
reply "250-PIPELINING"
|
294
|
+
reply "250-ENHANCEDSTATUSCODES"
|
295
|
+
@moreextensions.each{|ext| reply("250-#{ext}")}
|
296
|
+
reply "250 8BITMIME"
|
297
|
+
|
298
|
+
loop do
|
299
|
+
no_valid_recipients = true
|
300
|
+
|
301
|
+
catch :rset do
|
302
|
+
# Allow pipelining by accumulation
|
303
|
+
responses = []
|
304
|
+
loop do
|
305
|
+
line = gets
|
306
|
+
break if line =~ /^DATA$/
|
307
|
+
response = @headercb.call(line)
|
308
|
+
|
309
|
+
# Conform to section 4.2(2) of RFC 2033. We need at least one RCPT
|
310
|
+
# command to succeed, otherwise DATA further below must fail.
|
311
|
+
if line.start_with?("RCPT") && response.start_with?("2")
|
312
|
+
no_valid_recipients = false
|
313
|
+
end
|
314
|
+
|
315
|
+
responses << response
|
316
|
+
end
|
317
|
+
|
318
|
+
# Answer the pipeline
|
319
|
+
responses.each do |response|
|
320
|
+
reply response
|
321
|
+
end
|
322
|
+
|
323
|
+
# Prepare for receiving
|
324
|
+
reply "354 Start data. End with <CRLF>.<CRLF>"
|
325
|
+
message = ""
|
326
|
+
loop do
|
327
|
+
line = gets(true) # Keep carriage returns and prevent RSET
|
328
|
+
break if line.strip == "."
|
329
|
+
|
330
|
+
# Honour transparency process as per section 4.5.2 of RFC 821
|
331
|
+
line.slice!(0) if line.start_with?(".") && line.strip.length > 1
|
332
|
+
|
333
|
+
message << line
|
334
|
+
end
|
335
|
+
|
336
|
+
# Conform to section 4.2(2) of RFC 2033 by failing with 503 if no valid
|
337
|
+
# recipients were found.
|
338
|
+
if no_valid_recipients
|
339
|
+
log :info, "No valid RCPT commands received, denying relay."
|
340
|
+
reply "503 5.0.0 No valid RCPT command received, denying DATA."
|
341
|
+
next
|
342
|
+
end
|
343
|
+
|
344
|
+
begin
|
345
|
+
log :debug, "Invoking message callback."
|
346
|
+
@msgcb.call(message)
|
347
|
+
rescue => e
|
348
|
+
reply "551 Internal error: #{e.class}: #{e.message}"
|
349
|
+
@client.close
|
350
|
+
return
|
351
|
+
end
|
352
|
+
|
353
|
+
reply "250 2.6.0 #@successmsg"
|
354
|
+
|
355
|
+
final = gets
|
356
|
+
if final
|
357
|
+
if final =~ /^QUIT$/
|
358
|
+
# Regular QUIT
|
359
|
+
|
360
|
+
reply "221 2.0.0 #{@hostname} Goodbye."
|
361
|
+
@client.close
|
362
|
+
return
|
363
|
+
else
|
364
|
+
# Not closing connection, client wants to sent another email
|
365
|
+
end
|
366
|
+
else
|
367
|
+
# Whoops. Client closed connection without QUIT. Bad guy!
|
368
|
+
log :warning, "Client closed connection without QUIT."
|
369
|
+
@client.close
|
370
|
+
return
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def default_headercb(line)
|
377
|
+
"250 2.1.0 ok"
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lmtp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marvin Gülker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
This library allows your application to act as an LMTP endpoint
|
15
|
+
so you can have MTAs like Postfix relay mail directly to your
|
16
|
+
application.
|
17
|
+
email: quintus@quintilianus.eu
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- README.rdoc
|
22
|
+
- LICENSE
|
23
|
+
files:
|
24
|
+
- LICENSE
|
25
|
+
- README.rdoc
|
26
|
+
- lib/lmtp.rb
|
27
|
+
homepage: https://github.com/Quintus/ruby-lmtp
|
28
|
+
licenses:
|
29
|
+
- BSD
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options:
|
33
|
+
- "-t"
|
34
|
+
- LMTP library for Ruby
|
35
|
+
- "-m"
|
36
|
+
- README.rdoc
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.0.0
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.4.5
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: LMTP server library for Ruby
|
55
|
+
test_files: []
|