Compare commits

...

12 Commits
v0.4 ... master

Author SHA1 Message Date
Arthur POULET (via Mailinglistrb) 55b06e00bf
Update readme with more next steps 2023-06-22 14:00:54 +02:00
Arthur POULET 42d5ee0d83
Update sendmail instructions 2023-06-22 13:34:42 +02:00
Arthur POULET 9dd5c15593
Update readme with next step 2023-06-21 19:52:51 +02:00
Arthur POULET cbf43f741d
Fix distribution cache
The cache was not working properly as it intercepted all the emails
except for the first receiver.
2023-06-21 19:40:18 +02:00
Arthur POULET cb027227e9
Implemen list-users and more
Implement the list-users operation that was missing and add the FROM
environement config like SENDER.
2023-06-21 18:53:06 +02:00
Arthur POULET 45f1efb731
Improve IMAP and SENDER management
I changed my email provider recently and it seems I'm less free to set
the sender field now. So I cannot say this email comes from the real
user, I need to set it to the mailing list email, maybe the true email
behind the lists. This change allow this.

- Add some documentation (I did not updated mailinglistrb since some
  time so I needed to read my code again to understand what it does.
  This documentation should help any other user when configuring there
  own instance.
- Ignore more .env files, just in case
- Improve SMTP error handler (it does not do a lot of things but
  ensure we log unknown smtp errors as well)
- Add IMAP disconnection error management (it is still under test, I
  do not know for sure it fixed the issue where IMAP disconnected and
  not recovered. Also it is a bit clumsy as it does not ensure this at
  every step using the imap tcp socket.
- Add a SENDER config variable to force set sender field to something
  else than the original sender when distributing emails.
- Fix (probably ?) a bug where the port may not be taken into accound
  and ssl parameter neither. Did not tested it because I'm lazy.
- Replace mailinglist address suffix with + rather than . because it's
  probably a more common way to handle alias.
- Add more tasks to the Rakefile
2023-06-21 14:32:39 +02:00
Arthur POULET f10340a597
They say dependencies must be alpha sort 2022-11-26 10:49:56 +01:00
Arthur POULET 2585f58c69
Remove now useless dependencies semver, colorize 2022-11-26 10:49:19 +01:00
Arthur POULET 5b867e3d3e
Improve DB migrations
Previous db migration was a custom home-made system.
Now we use proper Sequel migrations.
2022-11-26 10:47:15 +01:00
Arthur POULET ce6853e99d
Fix when debug is disabled 2022-11-26 03:20:54 +01:00
Arthur POULET 08bfd0e888
Fix a few errors
- Fix autoregistration
- Fix validate distribute email encoding
2022-11-26 03:11:34 +01:00
Arthur POULET 9833e8fa54
Update readme 2022-11-26 02:30:28 +01:00
22 changed files with 241 additions and 165 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
.env .env*
*.db *.db
*.sqlite* *.sqlite*
README.html README.html
.yardoc/ .yardoc/
bin/db_seed.local

11
Gemfile
View File

@ -3,30 +3,27 @@
source "https://rubygems.org" source "https://rubygems.org"
# Mails proto # Mails proto
gem "net-smtp", "~> 0.3.3"
gem "net-imap", "~> 0.3.1" gem "net-imap", "~> 0.3.1"
gem "net-smtp", "~> 0.3.3"
# Mail template # Mail template
gem "erb", "~> 3.0" gem "erb", "~> 3.0"
# Database # Database
gem "sqlite3", "~> 1.5"
gem "sequel", "~> 5.62" gem "sequel", "~> 5.62"
gem "colorize", "~> 0.8.1" gem "sqlite3", "~> 1.5"
# Web # Web
gem "sinatra", "~> 3.0"
gem "puma", "~> 6.0" gem "puma", "~> 6.0"
gem "sinatra", "~> 3.0"
gem "slim", "~> 4.1" gem "slim", "~> 4.1"
# Others # Others
gem "dotenv", "~> 2.8" gem "dotenv", "~> 2.8"
gem "uuid", "~> 2.3" gem "uuid", "~> 2.3"
gem "semver", "~> 1.0"
# Debug and stuff # Debug and stuff
group :develop do group :develop do
gem "mocha", "~> 2.0"
gem "pry", "~> 0.14.1" gem "pry", "~> 0.14.1"
end end
gem "mocha", "~> 2.0"

View File

@ -3,7 +3,6 @@ GEM
specs: specs:
cgi (0.3.3) cgi (0.3.3)
coderay (1.1.3) coderay (1.1.3)
colorize (0.8.1)
dotenv (2.8.1) dotenv (2.8.1)
erb (3.0.0) erb (3.0.0)
cgi (>= 0.3.3) cgi (>= 0.3.3)
@ -30,7 +29,6 @@ GEM
rack-protection (3.0.3) rack-protection (3.0.3)
rack rack
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
semver (1.0.1)
sequel (5.62.0) sequel (5.62.0)
sinatra (3.0.3) sinatra (3.0.3)
mustermann (~> 3.0) mustermann (~> 3.0)
@ -52,7 +50,6 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
colorize (~> 0.8.1)
dotenv (~> 2.8) dotenv (~> 2.8)
erb (~> 3.0) erb (~> 3.0)
mocha (~> 2.0) mocha (~> 2.0)
@ -60,7 +57,6 @@ DEPENDENCIES
net-smtp (~> 0.3.3) net-smtp (~> 0.3.3)
pry (~> 0.14.1) pry (~> 0.14.1)
puma (~> 6.0) puma (~> 6.0)
semver (~> 1.0)
sequel (~> 5.62) sequel (~> 5.62)
sinatra (~> 3.0) sinatra (~> 3.0)
slim (~> 4.1) slim (~> 4.1)

View File

@ -37,6 +37,30 @@ Copy and fill all the env variables in .env
cp empty.env .env cp empty.env .env
edit .env edit .env
Here is the list of all the configuration available (sampled in empty.env):
SMTP_HOST information to connect on the smtp
SMTP_PORT
SMTP_TLS true or false
SMTP_USER
SMTP_PASSWORD
IMAP_HOST only imap available
IMAP_PORT
IMAP_TLS
IMAP_USER
IMAP_PASSWORD
SENDER_HOST the domain of the sender field, and the mailinglist addresses
SENDER "true" to retrive the true sender, "list" to use the mailing list email
else it is a static email address that will alway be used.
FROM same as SENDER
DB_URL sqlite://db/database.sqlite for instance
PORT for WWW accees, not used yet
DEBUG true/false
CPU_SLEEP slow down the distributor
LOG_FILE log stuff in the specified file
### Setup the database ### Setup the database
bin/db_migrate bin/db_migrate
@ -53,11 +77,25 @@ After deploying it, there are some tools:
- `bin/db_seed` to generate some data - `bin/db_seed` to generate some data
### Additional resources
[How to manage mailinglists](doc/manage_mailinglists.md)
## Contributing ## Contributing
### Next step
- [ ] Handle multi-modo (currently admin operations send the mail to the first modo only)
- [ ] Add web interface (for archives)
- [ ] Auto cut citations and mailinglist signatures
- [ ] Fetch history via email
- [ ] Global admin system via email
- [ ] Harder email security (check output server to verify authenticity)
- [ ] Disable signature by mailinglist
### via Gitea ### via Gitea
1. Fork it (<https://git.sceptique.eu/Sceptique/mailinglistrb>) 1. Fork it (<https://git.sceptique.eu/Sceptique/mailinglist.rb>)
2. Create your feature branch (`git checkout -b my-new-feature`) 2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`) 3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`) 4. Push to the branch (`git push origin my-new-feature`)
@ -65,9 +103,10 @@ After deploying it, there are some tools:
### via emails ### via emails
Checkout git-send-mail tutorial <https://git-send-email.io/> Checkout git-send-mail tutorial <https://git-send-email.io/>.
The mailing list is <mailto:list+mailinglistrb@sceptique.eu>
1. Clone it (<https://git.sceptique.eu/Sceptique/mailinglistrb>) 1. Clone it (<https://git.sceptique.eu/Sceptique/mailinglist.rb>)
2. Subscribe to the mailinglist to send your patch <mailto:list.mailinglistrb@sceptique.eu?subject=subscribe> (don't send your patch in this email) 2. Commit your changes (`git commit -am 'Add some feature'`)
3. Commit your changes (`git commit -am 'Add some feature'`) 3. Register the mailinglist by sending a "subscribe" to it
4. Send your email to <mailto:list.mailinglistrb@sceptique.eu> after you are validated by modo 4. Send your patch to the mailing list

View File

@ -7,6 +7,34 @@ Minitest::TestTask.create(:test) do |t|
t.test_globs = ["test/**/test_*.rb"] t.test_globs = ["test/**/test_*.rb"]
end end
namespace "db" do
desc "Migrate the database to the lasted schema"
task "migrate" do
load "bin/db_migrate"
end
desc "Initialize database with dumby data"
task "dumb_seed" do
load "bin/db_seed"
end
desc "Initialize database with local data"
task "local_seed" do
load "bin/db_seed.local"
end
desc "Reset all tables, schema, data"
task "reset" do
require_relative "lib/app"
$db.tables.each { $db.drop_table _1 }
end
namespace "reset" do
task "stuff" do
end
end
end
task :default => :test task :default => :test
task default: :test task default: :test

View File

@ -1,87 +1,7 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
$LOAD_PATH << File.join(Dir.pwd, "lib") $LOAD_PATH << File.join(Dir.pwd, "lib")
require "semver"
require "colorize"
require "app" require "app"
require "semver"
def current_version sequel_command = "bundle exec sequel -E -m db/migrations #{ENV['DB_URL']}"
$db[:meta].first[:version] rescue 0 exec(sequel_command)
end
def migrate(version, message = nil, &block)
puts "Check migration #{version}".on_blue
if current_version < version
puts "Migrate #{version}".blue
begin
yield
$db[:meta].update(version: version)
puts "Successfuly set version #{version}".green
puts message.green if message
rescue => err
puts err.message.on_red
exit 1
end
else
puts "Already migrated #{version}".yellow
end
end
migrate 1, "Initialized database" do
# this table only contains one entry
$db.create_table :meta do
primary_key :id
Int :version
String :code_version
String :code_date
end
$db[:meta].insert(version: 1)
end rescue puts "already initialized".yellow
migrate 2, "Initialize mailinglists" do
$db.create_table :mailinglists do
primary_key :id
column :name, String, null: false # Name to display
column :email, String, null: false # Mailing list email
column :count_handled, Integer # amount of email distributed
column :count_distributed, Integer # amount of email distributed
column :last_email, DateTime # date of last distribution
column :strategy, String, null: false # "free/validated/closed/..."
column :updated_at, DateTime
column :created_at, DateTime
index :name, unique: true
index :email, unique: true
end
end rescue puts "mailinglists already exists".yellow
migrate 3, "Initialize emails" do
$db.create_table :emails do
primary_key :id
foreign_key :mailinglist_id, :mailinglists, null: false #, on_delete: :cascade
column :name, String, null: false, fixed: true, size: 50
column :email, String, null: false, fixed: true, size: 254
column :permissions, Integer # bitfield. 1=read 2=write 4=admin
column :updated_at, DateTime
column :created_at, DateTime
index %i[email], unique: false # list personnal subscriptions
index %i[email mailinglist_id], unique: true
index %i[mailinglist_id], unique: false # allow to search entries by mailinglist
index %i[mailinglist_id permissions], unique: false # find readers, writers, etc.
end
end rescue puts "emails already exists".yellow
puts "End migration".on_blue
code_version = `git tag`.split("\n").map{ |str| Semver.new(str) }.sort.last
code_date = if code_version
`git show #{code_version} --pretty="format:%as"`.split("\n").first
else
Time.now
end
code_version ||= "v0.0.0"
$db[:meta].update(code_version: code_version.to_s)
$db[:meta].update(code_date: code_date.to_s)
puts "Set code version to #{code_version}".green
puts "Set code date to #{code_date}".green

View File

@ -0,0 +1,18 @@
Sequel.migration do
change do
create_table :mailinglists do
primary_key :id
column :name, String, null: false # Name to display
column :email, String, null: false # Mailing list email
column :count_handled, Integer # amount of email distributed
column :count_distributed, Integer # amount of email distributed
column :last_email, DateTime # date of last distribution
column :strategy, String, null: false # "free/validated/closed/..."
column :updated_at, DateTime
column :created_at, DateTime
index :name, unique: true
index :email, unique: true
end
end
end

View File

@ -0,0 +1,18 @@
Sequel.migration do
change do
create_table :emails do
primary_key :id
foreign_key :mailinglist_id, :mailinglists, null: false #, on_delete: :cascade
column :name, String, null: false, fixed: true, size: 50
column :email, String, null: false, fixed: true, size: 254
column :permissions, Integer # bitfield. 1=read 2=write 4=admin
column :updated_at, DateTime
column :created_at, DateTime
index %i[email], unique: false # list personnal subscriptions
index %i[email mailinglist_id], unique: true
index %i[mailinglist_id], unique: false # allow to search entries by mailinglist
index %i[mailinglist_id permissions], unique: false # find readers, writers, etc.
end
end
end

View File

@ -0,0 +1,43 @@
# Mailinglist management
## What are strategies?
There are 2 types of strategies: registration and moderation.
Registration defines how new user can access the list.
Moderation defines how user can write on the list.
### Registration
- autoregister: writing any email to the mailinglist will add the email of the user to the mailinglist
- free: any email can register to the list by sending a "subscribe" email to the list
- validated: like free but moderators needs to confirm the email with a "set-permissions" email. the user details are distributed to moderators first.
- closed: nobody can register via email
### Moderation
- freewrite: anyone registered can distribute emails to the list
- restrictedwrite: email needs approval by moderator before distribution. the email details are only distributed to moderators first.
## How to interact with the list by email ?
You need to send an email to the list with the subject being an action.
Some action can take parameters in the subject or the body.
Actions are:
- set-permissions
- validate
- refuse
- subscribe
- unsubscribe
- help
- list-users
- owner
Parameters are key=value, which are separated with "," in the subject or "new line" in the body.
### permissions parameters
permissions: a permission mask (0=reader, 1=writer, 2=operator, 4=moderator)
user-email: the email of the user
### validate parameters
uid: uid of the email

View File

@ -11,6 +11,8 @@ IMAP_USER=
IMAP_PASSWORD= IMAP_PASSWORD=
SENDER_HOST=machin.fr SENDER_HOST=machin.fr
SENDER=true
FROM=true
DB_URL="sqlite://dev.db" DB_URL="sqlite://dev.db"
PORT=10081 PORT=10081

View File

@ -39,7 +39,7 @@ class Distributor
"refuse" => Actions::RefuseDistribute.new(distributor: self), "refuse" => Actions::RefuseDistribute.new(distributor: self),
} }
end end
attr_reader :imap_client, :smtp_client, :handlers if $debug attr_reader :imap_client, :smtp_client, :handlers # if $debug # TODO: more alias for imap/smtp clients?
# Make sure any incoming email is properly directed to the right action. # Make sure any incoming email is properly directed to the right action.
def handle_one(mail) def handle_one(mail)
@ -50,7 +50,7 @@ class Distributor
attributes = Attributes.parse(subject: subject_attributes, body: mail.body) attributes = Attributes.parse(subject: subject_attributes, body: mail.body)
handler = @handlers[subject] || @handlers[:default] handler = @handlers[subject] || @handlers[:default]
$logger.info "#{handler.class}#handle on <#{list.email}> for <#{mail.from}>" $logger.info "#{handler.class}#handle on <#{list.email}> for <#{mail.from}>"
handler.handle(list:, mail:, attributes:) handler.handle(list:, mail: mail, attributes:)
else else
$logger.warn "list #{mail.to} do not exist (asked by #{mail.from})" $logger.warn "list #{mail.to} do not exist (asked by #{mail.from})"
end end

View File

@ -1 +0,0 @@
You have subscribed to <%= list.name %> <<%= list.email %>>

View File

@ -11,7 +11,7 @@ class Distributor
@distributor = distributor @distributor = distributor
end end
def handle(list:, to:, attributes:) def handle(list:, mail:, attributes:)
$logger.error "#{self.class} is not implemented yet" $logger.error "#{self.class} is not implemented yet"
end end
end end

View File

@ -91,7 +91,22 @@ class Distributor
end end
end end
LIST_USERS_TEMPLATE = Actions.template("list_users")
class ListUsers < Action class ListUsers < Action
def handle(list:, mail:, attributes:)
modo = Email.first(mailinglist: list, email: mail.from)
if !modo&.modo? && !modo&.op?
$logger.warn "SECU <#{mail.from}> failed to set-permissions <#{list.email}> modo"
return nil
end
body = LIST_USERS_TEMPLATE.result binding
@distributor.distribute(
Protocols::Mail.build(
subject: "ML #{list.name} user list", list:, to: modo.email, body:
)
)
end
end end
end end

View File

@ -82,8 +82,8 @@ class Distributor
if list.registration?("autoregister") if list.registration?("autoregister")
if user.nil? if user.nil?
$logger.info "registering <#{mail.from}> to <#{list.email}>" $logger.info "registering <#{mail.from}> to <#{list.email}>"
Email.register!(mailinglist: list, name: mail.from_name || mail.from.split("@").first, user = Email.register!(mailinglist: list, name: mail.from_name || mail.from.split("@").first,
email: mail.from).save email: mail.from).save
end end
# ok let the mail pass the security check # ok let the mail pass the security check
elsif !user&.writer? elsif !user&.writer?
@ -111,7 +111,8 @@ class Distributor
# TODO: this is the most lazy code I wrote this week I think # TODO: this is the most lazy code I wrote this week I think
# if multipart, get the boundary to add a new part in the top # if multipart, get the boundary to add a new part in the top
boundary = mail.header("Content-Type").to_s.match(/boundary="([^"]+)"/)[1] rescue nil boundary = mail.header("Content-Type").to_s.match(/boundary="([^"]+)"/)[1] rescue nil
body = WAIT_MODO_DISTRIBUTE_TEMPLATE.result binding wait_modo_distribute_template = Actions.template("distribute.wait_modo")
body = wait_modo_distribute_template.result binding
modo_mail = Protocols::Mail.build( modo_mail = Protocols::Mail.build(
subject: "ML #{list.name} requires validation for [#{mail.subject}]", subject: "ML #{list.name} requires validation for [#{mail.subject}]",
list:, to: modo.email, body:, list:, to: modo.email, body:,

View File

@ -1,5 +1,4 @@
<% if boundary %>--<%= boundary %> <% if boundary %>--<%= boundary %>
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"; protected-headers="v1" Content-Type: text/plain; charset="utf-8"; protected-headers="v1"
<% end %><%= mail.from %> Tried to send an email to <%= list.email %> <% end %><%= mail.from %> Tried to send an email to <%= list.email %>

View File

@ -0,0 +1,5 @@
List of the email subscribed to <%= list.name %> <<%= list.email %>>:
<% list.emails.each do |email| %>
- <%= email.name %> <<%= email.email %>> (permissions=<%= email.permissions %>, created <%= email.created_at %>)
<% end %>

View File

@ -1,7 +1,7 @@
require "uuid" require "uuid"
class Mailinglist < Sequel::Model($db) class Mailinglist < Sequel::Model($db)
SUFFIX_SEPARATOR = ENV["MAILINGLIST_SUFFIX_SEPARATOR"] || "." SUFFIX_SEPARATOR = ENV["MAILINGLIST_SUFFIX_SEPARATOR"] || "+"
BASE_USER = ENV["MAILINGLIST_BASE_USER"] || "mailinglist" BASE_USER = ENV["MAILINGLIST_BASE_USER"] || "mailinglist"
HOST = ENV["MAILINGLIST_HOST"] HOST = ENV["MAILINGLIST_HOST"]
@ -86,7 +86,7 @@ class Mailinglist < Sequel::Model($db)
ACTIONS_EMAILS = %i[ ACTIONS_EMAILS = %i[
help subscribe unsubscribe owner help subscribe unsubscribe owner
set_permissions list_users set_permissions list_users
] ].freeze
def help_email def help_email
"#{email}?subject=help" "#{email}?subject=help"
end end

View File

@ -2,7 +2,7 @@ require "net/imap"
# Protocls::Imap allows to fetch emails. # Protocls::Imap allows to fetch emails.
# #
# https://www.rfc-editor.org/rfc/rfc3501#page-54 # https://www.rfc-editor.org/rfc/rfc3501
# #
# TODO: strengthen the network management to avoid connection loss. # TODO: strengthen the network management to avoid connection loss.
class Protocols::Imap class Protocols::Imap
@ -17,7 +17,7 @@ class Protocols::Imap
attr_reader :imap if $debug attr_reader :imap if $debug
def reset! def reset!
@imap = Net::IMAP.new(ENV["IMAP_HOST"], ENV["IMAP_PORT"], ssl: true) @imap = Net::IMAP.new(ENV["IMAP_HOST"], port: ENV["IMAP_PORT"], ssl: true)
@imap.authenticate('PLAIN', ENV["IMAP_USER"], ENV["IMAP_PASSWORD"]) @imap.authenticate('PLAIN', ENV["IMAP_USER"], ENV["IMAP_PASSWORD"])
ALL_INBOXES.each do ALL_INBOXES.each do
$logger.info "Try to create inbox #{_1}" $logger.info "Try to create inbox #{_1}"
@ -31,10 +31,18 @@ class Protocols::Imap
# Fetch the next incomming email as a Protocols::Mail. # Fetch the next incomming email as a Protocols::Mail.
# #
# @return [Protocols::Mail] # @return [Protocols::Mail]
def fetch_next_unseen(inbox:) def fetch_next_unseen(inbox:, max_tries: 2)
uid = search_unseen(inbox:).first return $logger.error("fetch_next_unseen reached max_tries_limit") if max_tries == 0
return nil if uid.nil?
begin
uid = search_unseen(inbox:).first
rescue IOError => e
$logger.warn e.message
reset!
return fetch_next_unseen(inbox:, max_tries: max_tries - 1)
end
return nil if uid.nil?
fetch(uid:, inbox:) fetch(uid:, inbox:)
end end

View File

@ -11,8 +11,11 @@ class Protocols::Mail
HEADERS_KEEP = %w[ HEADERS_KEEP = %w[
Content-Type Content-Transfer-Encoding MIME-Version Content-Type Content-Transfer-Encoding MIME-Version
].freeze ].freeze
HEADERS_KEEP_WITH_ORIGINAL = HEADERS_KEEP + %w[Message-ID User-Agent From To Subject Subject Date In-Reply-To References] HEADERS_KEEP_WITH_ORIGINAL = HEADERS_KEEP + %w[Message-ID User-Agent From To Subject Subject Date In-Reply-To
References]
USER_AGENT = "Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}) Mailinglist.rb/1.0".freeze USER_AGENT = "Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}) Mailinglist.rb/1.0".freeze
SENDER = ENV["SENDER"]
FROM = ENV["FROM"]
# @params imap_mail is any element outputed in the returned array of # @params imap_mail is any element outputed in the returned array of
# Net::IMAP#fetch, and must contains some attributes: # Net::IMAP#fetch, and must contains some attributes:
@ -57,6 +60,11 @@ class Protocols::Mail
end end
end end
# This method is a kind of hash.
def cache_id
"#{@message_id} #{@from} #{@to}"
end
# Transform an received email into a mailing list email. # Transform an received email into a mailing list email.
# Tested with evolution and Kmail. # Tested with evolution and Kmail.
# Thunderbird bug at the moment which make it weird but it works too. # Thunderbird bug at the moment which make it weird but it works too.
@ -65,14 +73,34 @@ class Protocols::Mail
# @param dest [Email] # @param dest [Email]
def to_redistribute(list:, dest:) def to_redistribute(list:, dest:)
new = clone new = clone
sender =
if SENDER == "true"
@from
elsif SENDER == "list"
list.email
else
SENDER
end
from_email =
if FROM == "true"
@from
elsif FROM == "list"
list.email
else
FROM
end
from = "\"#{@from_name}\" (via #{list.name}) <#{from_email}>"
new.replace_headers!( new.replace_headers!(
["User-Agent", USER_AGENT], ["User-Agent", USER_AGENT],
# require people to do not respond privatly # require people to do not respond privatly
["Reply-to", "#{list.name} <#{list.email}>"], ["Reply-to", "#{list.name} <#{list.email}>"],
["Errors-To", list.email], ["Errors-To", list.email],
["To", dest.email], ["To", dest.email],
["From", "\"#{@from_name}\" (via #{list.name}) <#{@from}>"], ["From", from],
["Sender", @from], ["Sender", sender ],
["Date", Time.now.strftime(DATE_FORMAT)], ["Date", Time.now.strftime(DATE_FORMAT)],
["List-Id", "<#{list.email}>"], ["List-Id", "<#{list.email}>"],
["List-Post", "<mailto:#{list.email}>"], ["List-Post", "<mailto:#{list.email}>"],

View File

@ -8,7 +8,7 @@ class Protocols::Smtp
end end
class DistributedCache < Array class DistributedCache < Array
def initialize(max_size: 100) def initialize(max_size: 4096)
@max_size = max_size @max_size = max_size
super() super()
end end
@ -25,7 +25,7 @@ class Protocols::Smtp
# @param mail [Protocols::Mail] # @param mail [Protocols::Mail]
def distribute(mail) def distribute(mail)
if cache.include?(mail.message_id) if cache.include?(mail.cache_id)
$logger.warn "Already distributed #{mail.message_id}" $logger.warn "Already distributed #{mail.message_id}"
return return
end end
@ -34,7 +34,7 @@ class Protocols::Smtp
smtp_raw = mail.to_smtp smtp_raw = mail.to_smtp
$logger.debug smtp_raw.join("=====") $logger.debug smtp_raw.join("=====")
send_message_safe(*smtp_raw) send_message_safe(*smtp_raw)
cache << mail.message_id cache << mail.cache_id
rescue StandardError => e rescue StandardError => e
$logger.warn "FAILED #{mail.from}\t -> #{mail.to}:\t#{mail.subject}" $logger.warn "FAILED #{mail.from}\t -> #{mail.to}:\t#{mail.subject}"
$logger.debug e $logger.debug e
@ -61,6 +61,9 @@ class Protocols::Smtp
$logger.warn e.message $logger.warn e.message
reset_smtp_client! reset_smtp_client!
send_message_safe(*raw, max_tries: max_tries - 1) send_message_safe(*raw, max_tries: max_tries - 1)
rescue => e
$logger.error e.full_message
reset_smtp_client!
end end
end end
end end

View File

@ -1,44 +0,0 @@
class Semver
def initialize(string)
@values = string.gsub(/^v(\d.+)/, '\1').split(".").map(&:to_i)
end
def [](index)
@values[index] || 0
end
def to_s
"v#{@values.join('.')}"
end
def <=>(other)
cmp = 0
@values.each_with_index do |value, index|
cmp = value - other[index]
break if cmp != 0
end
cmp
end
def >(other)
(self <=> other) > 0
end
def <(other)
(self <=> other) < 0
end
def ==(other)
(self <=> other) == 0
end
def !=(other)
(self <=> other) != 0
end
end
# pp Utils::Semver.new("1.0") < Utils::Semver.new("1.1")
# pp Utils::Semver.new("0.1") < Utils::Semver.new("1.1")
# pp Utils::Semver.new("1.1") < Utils::Semver.new("1.2")
# pp Utils::Semver.new("1.1") < Utils::Semver.new("2.0")
# pp Utils::Semver.new("12.0") > Utils::Semver.new("2.2")