rubymta 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +0 -0
- data/README.md +345 -0
- data/lib/rubymta/base-x.rb +47 -0
- data/lib/rubymta/contact.rb +100 -0
- data/lib/rubymta/deepclone.rb +23 -0
- data/lib/rubymta/extended_classes.rb +168 -0
- data/lib/rubymta/item_of_mail.rb +113 -0
- data/lib/rubymta/queue_runner.rb +376 -0
- data/lib/rubymta/receiver.rb +615 -0
- data/lib/rubymta/server.rb +306 -0
- data/lib/rubymta/version.rb +5 -0
- data/lib/rubymta.rb +2 -0
- data/rubymta.gemspec +15 -0
- data/spec/coco +3 -0
- data/spec/rubymta.rb +199 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d94b02b7553df5c9c37317c7081949df6e2a1df6
|
4
|
+
data.tar.gz: c8963029d4eda72e12dda04fc7c7dfceebbbf266
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50b91a554c32a383688f18492125cd08186aba6ded6f0eda35459bb1ed321338f6fc32bccd2795668515678ce999910bbf517393d04186f0bf4bc512ab58fc43
|
7
|
+
data.tar.gz: 308793abfd6ed53b9a7b795ad2b4bb4558e24022c88b0f931c94bf2f3da300da3cf8d47abb9e99a49cda750bc42d5cfbc7bff9eee0740849f0fcd1184ce377e3
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,345 @@
|
|
1
|
+
# Ruby Mail Transport Agent (RubyMTA)
|
2
|
+
|
3
|
+
RubyMTA is a complete basic email server in a gem. It's completely written in Ruby and the configuration file is just a Ruby Module. RubyMTA is configured by setting a list of values, as well as extending the basic handlers for each of the SMTP handler methods. Because it's Ruby, you can override any method in the gem, if you need to.
|
4
|
+
|
5
|
+
It comes with a sample test configuration and a set of Bacon tests. (Requires installing the Bacon gem.)
|
6
|
+
|
7
|
+
## Disclaimer
|
8
|
+
|
9
|
+
This is experimental code which I've written for my own use. I'm happy to share it, and if it's useful to you in any way, I'm pleased about that. If you want to ask me questions, email me at mjwelchphd@gmail.com and I'll answer questions for free; but if you want me to write software for you, I'm available for hire.
|
10
|
+
|
11
|
+
There's a lot that still needs to be added, like bounce messages and forwarding. Also, while you can use `sqlite3 <database-name>` to view the database, you'll need to build yourself a 'control panel' to allow users to view and edit the database tables. It's almost a sure thing you'll be using a bigger database, like MySQL, Postgre, or Oracle, to store usernames and other data for your project, so you probably already hove some code for that. I implemented a control panel in a web site for the two SqLite3 tables, plus MySQL for the rest of the tables I use.
|
12
|
+
|
13
|
+
I'll make an effort to minimize the impact of future changes on the existing model, i.e., I'll try not to break anyone's working configuration, but I don't guarantee it. When software is this new and experimental, sometimes minor programming model changes are necessary.
|
14
|
+
|
15
|
+
If you want to contribure, go to GitHub and fork a copy. Submit pull requests for your changes, but remember, if you email me at mjwelchphd@gmail.com first, and make a proposal, I'll let you know ahead of time if I'll accept your PR. It could save you some time, and I may be able to give you some time-saving advice.
|
16
|
+
|
17
|
+
I wrote this gem because I've been using Exim4 (which is a an excellent general purpose mail transport agent), but Exim4 doesn't do a lot of things I want to do with an MTA. Exim4 and other general purpose MTAs were built from legacy rules and legacy code which, _in my opinion_, are now outdated. If Exim4 or other MTA you use does everything you want it to do, you should probably stick with it. I need to move into the future of email, so here I am.
|
18
|
+
|
19
|
+
RubyMTA is Ruby code. Do whatever you want with it. The only limitation is your imagination. It uses the outstanding Ruby gem _Sequel_, which Jeremy Evans calls [Sequel: The Database Toolkit for Ruby.](http://sequel.jeremyevans.net/) Sequel makes database operations a non-issue in programming, and it allows you to use almost any popular database available.
|
20
|
+
|
21
|
+
RubyMTA uses Ruby Hash objects to store items of email and other data, as Matz intended. You can add elements to an item of mail to suit your taste, and they are persistent. Once added, they can be accessed or manipulated anywhere until you delete them. It makes the code simple to read and understand. (I hate code only the author can read.)
|
22
|
+
|
23
|
+
Ayn Rand wrote in her novel, _Anthem_, “The secrets of this earth are not for all men to see, but only for those who will seek them.” If you want to know the details, study the code. It's not rocket science.
|
24
|
+
|
25
|
+
I use Linux Mint, but any linux will work. I don't use Windows, so if you want to use this gem on Windows, any assistance I can give you will probably be limited (but ask anyways). Sorry.
|
26
|
+
|
27
|
+
### Features of the Server
|
28
|
+
|
29
|
+
* It can listen on any number of ports simultaneously. These are usually 25, 467, and 587, the standard mail ports, but you can use any ports you want, if you have a special use for them.
|
30
|
+
* The server can run in user space, or as root. If you want to use the standard mail ports, the server must run as root. The server can run as a daemon.
|
31
|
+
* When a connection is made to the server, the server starts a separate receiver process to handle it.
|
32
|
+
* When properly configured, the receiver processes will lose their root privileges immediately after creation. This is a security feature which protects the server.
|
33
|
+
* The receiver supports TLS (the STARTTLS verb in SMTP).
|
34
|
+
* The receiver supports full authentication, but you must choose the method.
|
35
|
+
* A log file is built in.
|
36
|
+
* RubyMTA uses an SqLite3 database for two tables it uses to manage the state of the MTA.
|
37
|
+
* RubyMTA uses the Sequel gem for an ORM, so RubyMTA will support a range of databases, like MySQL and Postgre.
|
38
|
+
* RubyMTA runs until terminated by a `KILL -INT <pid>` or `^C`.
|
39
|
+
* A set of DNS queries is built in. These are used by the receiver to collect information about the sender and recipient.
|
40
|
+
* A SMTP server tester (to see if a given MX has a live mail server running) is built in.
|
41
|
+
* A method to validate AUTH PLAIN (Linux CRYPT) hashes. It's generally accepted that AUTH LOGIN is not needed because the server supports TLS.
|
42
|
+
|
43
|
+
### Features of the Receiver
|
44
|
+
|
45
|
+
* The receiver has several measures built-in that are designed to defeat spammers. They will be explained further in the configuration section.
|
46
|
+
* The internal format of the email and all the data collected about it is a Ruby Hash. You can add additional data to the hash as you find necessary to program any special features you want.
|
47
|
+
* The receiver has some built-in rules (or filters, if you wish to look at them like that).
|
48
|
+
* You can (in your configuration) extend any of the SMTP verb methods to add additional rules, perform operations on the data, and save information in the mail object.
|
49
|
+
* This is Ruby, so you can override or extend anything. There are things you can do easily in Ruby that you can't do at all in Courier, Exim4, or Postfix.
|
50
|
+
* The receiver adds the standard headers upon receipt of an email:
|
51
|
+
* Return-Path
|
52
|
+
* Delivered-To
|
53
|
+
* Received
|
54
|
+
* DKIM-Signature (which includes the above)
|
55
|
+
* The design is based on the idea of doing enough work during reception, that delivery is almost assured. For example, if an email is directed to a client, i.e., _local_ delivery, we can make sure that the client exists before accepting the email from the sender. In the case of a remote delivery, the existence of the server can be verified before accepting the email.
|
56
|
+
|
57
|
+
### Features of the Queue Runner
|
58
|
+
|
59
|
+
* The method `QueueRunner#run_queue` reads the queue and sorts the emails by domain and recipient in order to deliver all the recipients for a a give domain in a single parcel.
|
60
|
+
* It can deliver locally via LMTP (for Dovecot) or remotely via a remote server.
|
61
|
+
* You can program your own app to use `queue_runner` or write your own queue runner.
|
62
|
+
|
63
|
+
### TODO!
|
64
|
+
|
65
|
+
* The queue runner is a very basic class. Bounce and forwarding need to be implemented. Since I add a rule to reject relays in my server, bounce messages only need to be delivered locally with LMTP. In a relaying server, bounce messages may be sent back to a remote sender; if that address is spoofed, and it turns out to be a trap address, your server will get blacklisted. Hence the rule: I don't relay. There is an example rule in the demo configuration which implements a "no relay" error message, and now you know why email admins don't allow relays anymore.
|
66
|
+
|
67
|
+
### The Server is an Excellent Example of SSL Sockets
|
68
|
+
|
69
|
+
Most of the posts on the Internet on how to use SSL Sockets **_are wrong!_** Study `server.rb` to see how it's done correctly.
|
70
|
+
|
71
|
+
### This Version is Considered a Basic, but Stable Release
|
72
|
+
This server has been tested by sending it over 23,000 spam emails. No faults were found. It's licensed under the MIT license, so technically, you're on your own. But practically, drop me an email at mjwelchphd@gmail.com if you need help with this. I want it to be useful, stable, and reliable.
|
73
|
+
|
74
|
+
### Receive Rules As Of This Writing
|
75
|
+
|
76
|
+
#### On Connect
|
77
|
+
|
78
|
+
* Access TEMPORARILY denied
|
79
|
+
If the number of violations is equal to `MaxFailedMsgsPerPeriod`. If the number of violation exceeds `MaxFailedMsgsPerPeriod`, the connection is slammed shut (closed without further warning).
|
80
|
+
|
81
|
+
#### On EHLO or HELO
|
82
|
+
|
83
|
+
* Domain required after EHLO/HELO
|
84
|
+
This error will be returned if the value part of the EHLO statement is left blank.
|
85
|
+
* EHLO domain ... was not found in the DNS system
|
86
|
+
This error means that a DNS lookup of the value part of the EHLO statement came back empty. (The domain name given in the value part was not legitimate.)
|
87
|
+
|
88
|
+
#### On MAIL FROM
|
89
|
+
|
90
|
+
* No proper sender ... on the MAIL FROM line
|
91
|
+
This error will be returned if the value part of the MAIL FROM statement does not contain a properly formatted value: i.e., optional-name <username@domain.ext>.
|
92
|
+
* Local part ... cannot contain ...
|
93
|
+
Either the usage of dots ('.') is wrong, or illegal characters were found. Legal characters for this MTA are a-z, A_Z, 0-9, and !#\$%&'*+-/?^_`{|}~.
|
94
|
+
* Members must use port ...
|
95
|
+
If a sender is found in the user database (the sender is a member), (s)he must use port ... to send an email.
|
96
|
+
* Traffic on port ... must be authenticated
|
97
|
+
Members must send emails on an authenticated, encrypted port.
|
98
|
+
* Traffic on port ... must be encrypted
|
99
|
+
Members must send emails on an authenticated, encrypted port.
|
100
|
+
* Non members must use port ...
|
101
|
+
Non-members may not use any port except the `StandardMailPort`.
|
102
|
+
|
103
|
+
#### RCPT TO
|
104
|
+
|
105
|
+
* No proper recipient ... on the RCPT TO line
|
106
|
+
This error will be returned if the value part of the RCPT TO statement does not contain a properly formatted value: i.e., optional-name <username@domain.ext>.
|
107
|
+
|
108
|
+
#### DATA
|
109
|
+
|
110
|
+
* There must be at least 1 acceptable recipient
|
111
|
+
This error will be returned if all the recipients in the RCPT TO lines were rejected.
|
112
|
+
* Error: unable to save packet id=...
|
113
|
+
This error will be returned if the write to the `packets` table fails.
|
114
|
+
* Error: unable to save queue id=...
|
115
|
+
This error will be returned if the ItemOfMail object could not be saved to the `queue` directory.
|
116
|
+
|
117
|
+
### The `contacts` Table
|
118
|
+
|
119
|
+
RubyMTA makes an entry into it's `contacts` table in the SqLite3 database every time there is a connection. It keeps track of the number of times a sender has connected, but more importantly, it counts the number of _violations_ and when `MaxFailedMsgsPerPeriod` is reached, RubyMTA refuses the connection with a warning message, and sets a lockout for `ProhibitedSeconds` seconds. If yet another connection is attempted during the lockout period, RubyMTA slams the connection shut until the lockout period has passed.
|
120
|
+
|
121
|
+
### The `parcels` Table
|
122
|
+
|
123
|
+
Every time a valid email is received, an entry is placed into the parcels table for each recipient of the given email. As emails are successfully delivered, the delivery time is put into the table for that recipient, along with the last server message. This table is used by the `queue_runner` to schedule delivery of mail. It's also useful to see why a parcel was undeliverable, in the case delivery fails.
|
124
|
+
|
125
|
+
This feature stops spammers and hackers from repeatedly connecting in an attempt to hack the server.
|
126
|
+
|
127
|
+
## How to Get the Gem
|
128
|
+
|
129
|
+
You can get the gem's source code on GitHub:
|
130
|
+
```bash
|
131
|
+
git clone https://github.com/mjwelchphd/rubymta.git
|
132
|
+
```
|
133
|
+
|
134
|
+
To update your copy, just use:
|
135
|
+
```bash
|
136
|
+
git pull
|
137
|
+
```
|
138
|
+
|
139
|
+
You can also get the gem on rubygems.org:
|
140
|
+
```bash
|
141
|
+
sudo gem install rubymta
|
142
|
+
```
|
143
|
+
|
144
|
+
You will also a few other gems:
|
145
|
+
```bash
|
146
|
+
sudo gem install bacon pdkim pretty_inspect unix-crypt
|
147
|
+
```
|
148
|
+
|
149
|
+
# Gem Dependencies
|
150
|
+
This gem requires the following (in alphabetical order):
|
151
|
+
```ruby
|
152
|
+
require "bacon"
|
153
|
+
require "base64"
|
154
|
+
require "etc"
|
155
|
+
require "logger"
|
156
|
+
require "openssl"
|
157
|
+
require "optparse"
|
158
|
+
require "ostruct"
|
159
|
+
require "pdkim"
|
160
|
+
require "pretty_inspect"
|
161
|
+
require "resolv"
|
162
|
+
require "sequel"
|
163
|
+
require "socket"
|
164
|
+
require "sqlite3"
|
165
|
+
require "timeout"
|
166
|
+
require "unix_crypt"
|
167
|
+
```
|
168
|
+
All of these packages are found in the Ruby Standard Library (stdib 2.2.2 at the time of this writing), except bacon, pdkim, pretty_inspect, and unix-crypt, which you will have to install. They are required in the gem itself, so you don't have to require them.
|
169
|
+
|
170
|
+
### The Working Demo
|
171
|
+
|
172
|
+
There is a working demo that you can configure to experiment with RubyMTA, or for your own setup. This demo program is a good place for you to start to build your own program. It's located inside the gem in a directory called "gmta."
|
173
|
+
|
174
|
+
Copy that to your own directory, and edit it according to the parameters below. Here's the configuration file I use for testing the gem:
|
175
|
+
|
176
|
+
```bash
|
177
|
+
module Config
|
178
|
+
# server configuration
|
179
|
+
ServerTitle = "Test Mail"
|
180
|
+
ServerName = "mail.tzarmail.com" # server name used in messages and EHLO
|
181
|
+
PostMasterName = "postmaster@tzarmail.com"
|
182
|
+
StandardMailPort = '25' #'25'--non client must come in here
|
183
|
+
InternalSubmitPort = '467' #'467'--internal port
|
184
|
+
SubmissionPort = '587' #'587'--client must come in here
|
185
|
+
# StandardMailPort = '2000' #'25'--non client must come in here
|
186
|
+
# InternalSubmitPort = '2001' #'467'--internal port
|
187
|
+
# SubmissionPort = '2002' #'587'--client must come in here
|
188
|
+
|
189
|
+
LocalLMTPPort = '24' #'24'--for sending to dovecot
|
190
|
+
ListeningPorts = [StandardMailPort,InternalSubmitPort,SubmissionPort]
|
191
|
+
UserName = "devel" # must be present if rubymta run as root
|
192
|
+
GroupName = "devel" # must be present if rubymta run as root
|
193
|
+
# UserName = nil # must be present if rubymta run as root
|
194
|
+
# GroupName = nil # must be present if rubymta run as root
|
195
|
+
|
196
|
+
LockFilePath = "#{$app[:path]}/gmta.lock"
|
197
|
+
PrivateKey = "#{$app[:path]}/gmta.key" # filename or nil TODO! all $app[:path] have to come from the $app[:dir]
|
198
|
+
Certificate = "#{$app[:path]}/gmta.crt" # filename or nil
|
199
|
+
# PrivateKey = nil
|
200
|
+
# Certificate = nil
|
201
|
+
S3DBPath = "#{$app[:path]}/gmta-dev.db"
|
202
|
+
LogPathAndFile = "/var/log/rubymta/rubymta.log" # log file location
|
203
|
+
LogFileLife = "daily" # log rotation control
|
204
|
+
PidPath = "/var/run/rubymta" # path to the directory where rubymta.pid will be stored
|
205
|
+
|
206
|
+
# receiver configuration
|
207
|
+
ReceiverTimeout = 30 # seconds
|
208
|
+
RemoteSMTPPort = 25 # port 25 is the outgoing submitter port
|
209
|
+
ProhibitedSeconds = 3600 # number of seconds prohibition is enforced
|
210
|
+
MaxFailedMsgsPerPeriod = 3 # number of violations before IP is prohibited
|
211
|
+
ShowIncomingData = false # true for testing--creats giant logs for giant emails
|
212
|
+
EhloDomainRequired = true # the email rules require this
|
213
|
+
EhloDomainVerifies = true # the domain must exist in the DNS system
|
214
|
+
DumpMailIntoLog = false # true for testing--creates giant logs
|
215
|
+
DisplayReceiverDialog = true # this displays the received dialog on the display
|
216
|
+
LogReceiverConversation = true # enables the logging of the incoming conversation
|
217
|
+
|
218
|
+
# item of mail configuration
|
219
|
+
MessageIdBase = 62 # 62 for Linux, 36 for OSX and Cygwin
|
220
|
+
MailQueue = "#{$app[:path]}/queue"
|
221
|
+
|
222
|
+
# transporter configuration
|
223
|
+
QueueRunnerTimeout = 30
|
224
|
+
DisplayQueueRunnerDialog = true # this displays the transported dialog on the display
|
225
|
+
LogQueueRunnerConversation = false # enables the logging of the outgoing conversation
|
226
|
+
DKIMPrivateKeyFile = "dkim.private.key"
|
227
|
+
end
|
228
|
+
|
229
|
+
# the test password is 'my-password' --
|
230
|
+
# this should be replaced by a database lookup
|
231
|
+
Users = {'coco@tzarmail.com'=>{:id=>1, :passwd=>"$5$BsHk6IIvndgdBmo9$iuO6WMaXzgzpGmGreV4uiH72VRGG1USNK/e5tL7P9jC"},
|
232
|
+
'mike@tzarmail.com'=>{:id=>2, :passwd=>"$5$BsHk6IIvndgdBmo9$iuO6WMaXzgzpGmGreV4uiH72VRGG1USNK/e5tL7P9jC"}}
|
233
|
+
|
234
|
+
class Receiver
|
235
|
+
|
236
|
+
#*************************************************************************
|
237
|
+
#*** This is a special override which always returns 3 arguments ***
|
238
|
+
#*** They are: :id, :owner_id, and either :local or :remote depending ***
|
239
|
+
#*** on whether the email belongs to us or not. In the case that the ***
|
240
|
+
#*** name is not found, it returns [nil, nil, :remote] ***
|
241
|
+
#*************************************************************************
|
242
|
+
|
243
|
+
def client_lookup(email)
|
244
|
+
# check to see if this email is a client and
|
245
|
+
# get both the mailbox_id and owner_id for use later --
|
246
|
+
# the question, "is it a client?" can be answered
|
247
|
+
# like: if @mail[:mailfrom][:mailbox_id] ...
|
248
|
+
if user = Users[email]
|
249
|
+
[user[:id],1,:local]
|
250
|
+
else
|
251
|
+
[nil, nil, :remote]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
#*************************************************************************
|
256
|
+
#*** The remaining overrides get the value (the received command line) ***
|
257
|
+
#*** and return either an error string or array of strings ***
|
258
|
+
#*************************************************************************
|
259
|
+
|
260
|
+
def auth(value)
|
261
|
+
auth_type, auth_encoded = value.split
|
262
|
+
# auth_encoded contains both username and password
|
263
|
+
case auth_type.upcase
|
264
|
+
when "PLAIN"
|
265
|
+
# get the password hash from the database
|
266
|
+
username, ok = auth_encoded.validate_plain do |username|
|
267
|
+
# the password hash is for "my-password"
|
268
|
+
# this should be replaced by a database lookup
|
269
|
+
passwd = Users[username][:passwd]
|
270
|
+
end
|
271
|
+
if ok
|
272
|
+
@mail[:authenticated] = username
|
273
|
+
return "235 2.0.0 Authentication succeeded"
|
274
|
+
else
|
275
|
+
return "530 5.7.8 Authentication failed"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
|
281
|
+
def rcpt_to(value)
|
282
|
+
# this is a sample rule that disallows relaying
|
283
|
+
from = @mail[:mailfrom]
|
284
|
+
rcpt = @mail[:rcptto].last
|
285
|
+
if from[:owner_id].nil? && rcpt[:owner_id].nil?
|
286
|
+
@contact.violation
|
287
|
+
LOG.info("%06d"%Process::pid) {"Mail from #{from[:url]} to #{rcpt[:url]} was rejected because it was a relay"}
|
288
|
+
return "556 5.7.27 This server does not support relaying"
|
289
|
+
end
|
290
|
+
nil
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
```
|
295
|
+
|
296
|
+
### Configuration Parameters
|
297
|
+
|
298
|
+
| Parameter | Description |
|
299
|
+
| --- | --- |
|
300
|
+
| ServerTitle | Choose an appropriate name, such as "ABC Company Mail Server" |
|
301
|
+
| ServerName | Use the server's *_domain_* name, i.e., `mail.abc.com`. |
|
302
|
+
| PostMasterName | Use the email address _to which the postmaster's mail should be directed_, i.e., `jim-malone@abc.com`.
|
303
|
+
| StandardMailPort | The standard mail port is 25. If you are testing, you might use a port like 2000 (which is above 1023 and doesn't require you to run the server as `root`. |
|
304
|
+
| InternalSubmitPort | The internal mail submission port is 467. If you are testing, you might use a port like 2001 (which is above 1023 and doesn't require you to run the server as `root`. |
|
305
|
+
| SubmissionPort | The standard mail submission (for clients) port is 587. If you are testing, you might use a port like 2002 (which is above 1023 and doesn't require you to run the server as `root`. |
|
306
|
+
| LocalLMTPPort | The port commonly used for internal submission (to Dovecot) is 24, but as long as you use the same port in Dovecot's configuration files, it doesn't matter what port you use. |
|
307
|
+
| UserName | Use the the login name under which the receiver will receive the email, once it is passed a connection from the server. This is optional if you are not going to run the MTA as root. |
|
308
|
+
| GroupName | Use the group name under which the receiver will receive the email, once it is passed a connection from the server. This is optional if you are not going to run the MTA as root. |
|
309
|
+
| LockFilePath | Use a name where the lock file will be located. It may be best to just follow the pattern. Make sure that if you run RubyMTA as root that the lock file is available to the UserName/GroupName also. |
|
310
|
+
| PrivateKey | This is the name of the private key file for encrypting/decrypting TLS. If you are not going to support TLS, this can be nil. |
|
311
|
+
| Certificate | This is the name of the certificate file for encrypting/decrypting TLS. If you are not going to support TLS, this can be nil. |
|
312
|
+
| S3DBPath | Use the name of the SqLite3 file which will contain the `contacts` and `parcels` tables used by the RubyMTA. The first time the server is started, if the file is not there, RubyMTA will create it and its tables. You can edit the database using the `sqlite3 <database>` command.
|
313
|
+
| LogPathAndFile | Use any location you want for the log file, but the log file _is not_ optional. Make sure that if you run RubyMTA as root that the log file is available to the UserName/GroupName also, or `run_queue` will fail. |
|
314
|
+
| LogFileLife | See the `logger` ruby gem for acceptable values. |
|
315
|
+
| PidPath | Use any location you want, but make sure that if you run RubyMTA as root that the log file is available to the UserName/GroupName also. |
|
316
|
+
| ReceiverTimeout | The default value of 30 seconds is good. You can experiment with this value, but normally, you will have very few connections that will need to be timed out (just some wierd spammer thing, maybe). |
|
317
|
+
| RemoteSMTPPort | The standard mail port is 25. This is the port used by the `queue_runner` for outgoing remote SMTP mail. |
|
318
|
+
| ProhibitedSeconds | Use the number of seconds you want to lock out a badly behaved sender. I've seen spammers send messages as slowly as every 15 minutes, so I used 3600 seconds as a default. |
|
319
|
+
| MaxFailedMsgsPerPeriod | Use the number of violations a sender can have before getting rejected with a warning. On the `MaxFailedMsgsPerPeriod`th + 1 connection, RubyMTA will slam the port shut without a warning to the sender. After the `ProhibitedSeconds` lockout period has passed without a connection attempt, the prohibition is removed. |
|
320
|
+
| ShowIncomingData | If true, logs the incoming data in the DATA section of an email. This can produce giant logs, and only should be used for debugging. Set to false. |
|
321
|
+
| EhloDomainRequired | If true, the receiver will make sure there is a domain name following the EHLO (or HELO) verb. This should be set to `true` because email rules require it. |
|
322
|
+
| EhloDomainVerifies | Validate the domain name given in the EHLO (or HELO) verb using DNS. |
|
323
|
+
| DumpMailIntoLog | If true, this dumps the ItemOfMail hash into the log for debugging. It should only be used for debugging. The dump is identical to the data stored in the email in the queue directory. |
|
324
|
+
| DisplayReceiverDialog | This variable is like LogReceiverConversation, but displays on the screen rather than go to the log. |
|
325
|
+
| LogReceiverConversation | If true, the dialog between the sender and the receiver is logged. This flag is usually used for debugging, but it is also useful to see the dialog when an attacker is trying to connect with an unknown command sequence. |
|
326
|
+
| MessageIdBase | Linux filenames are case sensitive, so this can be set to 62. OSX and Cygwin are not, so this must be set to 36. You can set it to 36 for Linux, but that would be ugly. |
|
327
|
+
| MailQueue | Use the path of the directory where ItemOfMails will be stored. |
|
328
|
+
| QueueRunnerTimeout | The default value of 30 seconds is good, but you can experiment with this value if you are sending remote mail, and having trouble with a particular network route timing out. |
|
329
|
+
| DisplayQueueRunnerDialog | This variable is like LogQueueRunnerConversation, but displays on the screen rather than go to the log. |
|
330
|
+
| LogQueueRunnerConversation | If true, the dialog between the queue_runner and Dovecot or the remote server is logged. This flag is usually used for debugging, but it is also useful to see the dialog when you are having trouble communicating with a particular remote server. |
|
331
|
+
| DKIMPrivateKeyFile | Use the path and name of the _private_ DKIM key, if you want to support DKIM, or nil if not. (The public key goes into the server's DNS records.)
|
332
|
+
|
333
|
+
### Configuration Extensions
|
334
|
+
|
335
|
+
Each verb (EHLO, MAIL FROM, etc.) can have an _extension_. After the built in processing is complete, if you have an extension method in your configuration file, it will be called. It must return either nil or a message that will be returned by the verb.
|
336
|
+
|
337
|
+
For example, if I want to check for a relay (remote sender plus remote recipient), I can use a method in the `class Receive` in my configuration file, like the one in the example above.
|
338
|
+
|
339
|
+
There are two required extensions, `client_lookup` and `auth`.
|
340
|
+
|
341
|
+
The `client_lookup` extension looks in the user list (which may be any source of your choosing), and returns three values: (1) the record ID for the mailbox, and (2) the record ID of the owner of the mailbox, and (3) the value _:local_ or _:remote_, as appropriate. This is by `queue_runner` to deliver the email.
|
342
|
+
|
343
|
+
The `auth` extension validates the user's password. Normally, a _client_ must log into the server to _send_ mail. The reason is to prevent spoofing. *__This basic MTA does not contain the rules to enforce this. It is left to the programmer to program those and any other rules he wants.__* Use the example to see how this is usually done. Note that a dummy list is inserted into the demo for testing.
|
344
|
+
|
345
|
+
<hr/><center>Fin.</center>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# this is used to convert numbers in the email IDs back to base 10
|
4
|
+
def from_b(base=62)
|
5
|
+
n = 0
|
6
|
+
self.each_char do |ch|
|
7
|
+
n = n*base
|
8
|
+
m = ch.ord
|
9
|
+
case
|
10
|
+
when m>=97
|
11
|
+
k = m-61
|
12
|
+
when m>=65
|
13
|
+
k = m-55
|
14
|
+
when m>=48
|
15
|
+
k = m-48
|
16
|
+
end
|
17
|
+
n += k
|
18
|
+
end
|
19
|
+
return n
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class Fixnum
|
25
|
+
|
26
|
+
# this is used to convert a number into segments of
|
27
|
+
# base 62 (or 36) for use in creating email IDs
|
28
|
+
def to_b(base=62)
|
29
|
+
n = self
|
30
|
+
r = ""
|
31
|
+
while n > 0
|
32
|
+
m = n%base
|
33
|
+
n /= base
|
34
|
+
case
|
35
|
+
when m>=36
|
36
|
+
k = m+61
|
37
|
+
when m>=10
|
38
|
+
k = m+55
|
39
|
+
when m>=0
|
40
|
+
k = m+48
|
41
|
+
end
|
42
|
+
r << k.chr
|
43
|
+
end
|
44
|
+
return r.reverse
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class Contact < Hash
|
2
|
+
|
3
|
+
def initialize(remote_ip)
|
4
|
+
# Reset timed-out records
|
5
|
+
S3DB[:contacts].where("expires_at<'#{Time.now.strftime("%Y-%m-%d %H:%M")}'").update(:violations=>0)
|
6
|
+
|
7
|
+
# See if it's already there
|
8
|
+
rs = S3DB[:contacts].where(:remote_ip=>remote_ip).first
|
9
|
+
if rs.nil?
|
10
|
+
# No, add one
|
11
|
+
self[:id] = nil
|
12
|
+
self[:remote_ip] = remote_ip
|
13
|
+
self[:hits] = self[:locks] = self[:violations] = 0
|
14
|
+
self[:created_at] = Time.now
|
15
|
+
id = S3DB[:contacts].insert(self)
|
16
|
+
self[:id] = id
|
17
|
+
else
|
18
|
+
# Yes, copy the data
|
19
|
+
rs.each { |k,v| self[k] = v }
|
20
|
+
end
|
21
|
+
# Set up the data-set for use later
|
22
|
+
@ds = S3DB[:contacts].select(:id, :remote_ip, :hits, :locks, :violations, :expires_at, :created_at, :updated_at).where(:id=>self[:id])
|
23
|
+
|
24
|
+
# count the hit and set the flag to count only one violation per invocation
|
25
|
+
self[:hits] += 1
|
26
|
+
@inhibit = false
|
27
|
+
modify
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove
|
31
|
+
# Remove is rarely used, if at all, because the contacts
|
32
|
+
# table keeps a history of IPs that have connected
|
33
|
+
if !self[:id].nil?
|
34
|
+
@ds.delete
|
35
|
+
self[:id] = nil
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def modify
|
41
|
+
# Modify resets the 'expires_at' value after any change to
|
42
|
+
# then record, but it's only significant when 'violations'
|
43
|
+
# is at or above ProhibitedSeconds
|
44
|
+
expires_at = Time.now + ProhibitedSeconds
|
45
|
+
if !self[:id].nil?
|
46
|
+
self[:expires_at] = expires_at
|
47
|
+
self[:updated_at] = Time.now
|
48
|
+
@ds.update(self)
|
49
|
+
end
|
50
|
+
expires_at
|
51
|
+
end
|
52
|
+
|
53
|
+
def violation
|
54
|
+
# Count the violation and reset the 'expires_at' time -- Also
|
55
|
+
# counts the times this IP has been locked out -- only count
|
56
|
+
# one violation and one lock per instantiation
|
57
|
+
if !inhibited?
|
58
|
+
self[:violations]+=1
|
59
|
+
@inhibit = true
|
60
|
+
if prohibited?
|
61
|
+
self[:locks] += 1
|
62
|
+
self[:expires_at] = Time.now + ProhibitedSeconds
|
63
|
+
end
|
64
|
+
modify
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def inhibited?
|
69
|
+
@inhibit
|
70
|
+
end
|
71
|
+
|
72
|
+
def violations?
|
73
|
+
# Returns the current count
|
74
|
+
self[:violations]
|
75
|
+
end
|
76
|
+
|
77
|
+
def warning?
|
78
|
+
# Returns true or false
|
79
|
+
self[:violations] >= MaxFailedMsgsPerPeriod
|
80
|
+
end
|
81
|
+
|
82
|
+
def prohibited?
|
83
|
+
# Returns true or false
|
84
|
+
self[:violations] > MaxFailedMsgsPerPeriod
|
85
|
+
end
|
86
|
+
|
87
|
+
# set this one to prohibited
|
88
|
+
def prohibit
|
89
|
+
self[:violations] = MaxFailedMsgsPerPeriod+1
|
90
|
+
modify
|
91
|
+
end
|
92
|
+
|
93
|
+
def allow
|
94
|
+
self[:violations] = 0
|
95
|
+
modify
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Object
|
2
|
+
# deepclone not only clones the target object, but all
|
3
|
+
# objects inside of it, i.e., if you clone a hash of
|
4
|
+
# other objects, those other objects will also be cloned.
|
5
|
+
def deepclone
|
6
|
+
case
|
7
|
+
when self.class==Hash
|
8
|
+
hash = {}
|
9
|
+
self.each { |k,v| hash[k] = v.deepclone }
|
10
|
+
hash
|
11
|
+
when self.class==Array
|
12
|
+
array = []
|
13
|
+
self.each { |v| array << v.deepclone }
|
14
|
+
array
|
15
|
+
else
|
16
|
+
if defined?(self.class.new)
|
17
|
+
self.class.new(self)
|
18
|
+
else
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|