rubymta 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ log/
2
+ queue/
3
+ *.gem
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