sugoi-mail 0.0.0
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.
- data/README +35 -0
- data/Rakefile +10 -0
- data/app/apis/mailservice_api.rb +178 -0
- data/app/controllers/.sugoi_admin_controller.rb.swp +0 -0
- data/app/controllers/account_controller.rb +39 -0
- data/app/controllers/address_controller.rb +49 -0
- data/app/controllers/application.rb +4 -0
- data/app/controllers/commandline_controller.rb +15 -0
- data/app/controllers/domain_controller.rb +37 -0
- data/app/controllers/mailinglist_controller.rb +51 -0
- data/app/controllers/mailservice_controller.rb +497 -0
- data/app/controllers/sugoi_admin_controller.rb +93 -0
- data/app/helpers/account_helper.rb +2 -0
- data/app/helpers/address_helper.rb +2 -0
- data/app/helpers/application_helper.rb +3 -0
- data/app/helpers/domain_helper.rb +2 -0
- data/app/helpers/mailservice_helper.rb +2 -0
- data/app/helpers/sugoi_admin_helper.rb +2 -0
- data/app/models/address.rb +110 -0
- data/app/models/admin_message.rb +45 -0
- data/app/models/confirmationcode.rb +59 -0
- data/app/models/domain.rb +76 -0
- data/app/models/mailinglist.rb +336 -0
- data/app/models/mailinglist_class.rb +39 -0
- data/app/models/message.rb +293 -0
- data/app/models/proxy_link.rb +25 -0
- data/app/models/user.rb +150 -0
- data/app/views/account/login.rhtml +22 -0
- data/app/views/account/logout.rhtml +10 -0
- data/app/views/account/signup.rhtml +17 -0
- data/app/views/account/welcome.rhtml +13 -0
- data/app/views/address/_form.rhtml +7 -0
- data/app/views/address/edit.rhtml +10 -0
- data/app/views/address/list.rhtml +27 -0
- data/app/views/address/new.rhtml +8 -0
- data/app/views/address/show.rhtml +8 -0
- data/app/views/domain/login.rhtml +22 -0
- data/app/views/domain/logout.rhtml +10 -0
- data/app/views/domain/signup.rhtml +17 -0
- data/app/views/domain/welcome.rhtml +13 -0
- data/app/views/layouts/address.rhtml +13 -0
- data/app/views/layouts/scaffold.rhtml +13 -0
- data/app/views/mailinglist/_form.rhtml +28 -0
- data/app/views/mailinglist/edit.rhtml +10 -0
- data/app/views/mailinglist/list.rhtml +27 -0
- data/app/views/mailinglist/new.rhtml +8 -0
- data/app/views/mailinglist/show.rhtml +16 -0
- data/app/views/sugoi_admin/create_domain.rhtml +1 -0
- data/app/views/sugoi_admin/list_addresses.rhtml +1 -0
- data/app/views/sugoi_admin/list_domains.rhtml +2 -0
- data/app/views/sugoi_admin/list_mailinglists.rhtml +1 -0
- data/bin/mailc +32 -0
- data/bin/maild +133 -0
- data/bin/sugoi-admin +21 -0
- data/bin/sugoi-mail +20 -0
- data/config/boot.rb +44 -0
- data/config/environment.rb +54 -0
- data/config/environments/bench.rb +21 -0
- data/config/environments/coverage.rb +21 -0
- data/config/environments/development.rb +21 -0
- data/config/environments/production.rb +18 -0
- data/config/environments/test.rb +19 -0
- data/config/lighttpd.conf +46 -0
- data/config/routes.rb +29 -0
- data/db/migrate/001_mailproxy.rb +7 -0
- data/db/migrate/002_create_users.rb +13 -0
- data/db/migrate/003_create_mailinglists.rb +13 -0
- data/db/migrate/004_create_addresses.rb +12 -0
- data/db/migrate/005_create_addresses_mailinglists.rb +13 -0
- data/db/migrate/006_alter_mailinglists.rb +9 -0
- data/db/migrate/007_create_messages.rb +25 -0
- data/db/migrate/008_add_mailinglistid_to_users.rb +14 -0
- data/db/migrate/009_add_domainadmin_to_users.rb +9 -0
- data/db/migrate/010_add_domain_to_users.rb +16 -0
- data/db/migrate/011_add_active_to_addresses.rb +14 -0
- data/db/migrate/012_create_confirmationcodes.rb +14 -0
- data/db/migrate/013_add_description_to_mailinglists.rb +9 -0
- data/db/migrate/014_create_admin_messages.rb +69 -0
- data/db/migrate/015_add_messages_to_mailinglists.rb +26 -0
- data/db/migrate/016_add_mailinglist_admin_to_users.rb +9 -0
- data/db/migrate/017_add_mailinglist_types.rb +94 -0
- data/db/migrate/018_add_bounciness_to_addresses.rb +20 -0
- data/db/migrate/019_add_archived_to_mailinglist_classes.rb +25 -0
- data/db/migrate/020_add_envelope_data_to_messages.rb +11 -0
- data/db/migrate/021_add_addresses_users.rb +14 -0
- data/db/migrate/022_add_virtual_to_users.rb +14 -0
- data/db/migrate/023_drop_openposting_from_mailinglists.rb +9 -0
- data/db/migrate/024_add_proxy_links.rb +21 -0
- data/db/migrate/025_add_proxify_to_mailinglist_classes.rb +25 -0
- data/db/schema.mysql.sql +104 -0
- data/db/schema.postgresql.sql +104 -0
- data/db/schema.rb +85 -0
- data/db/schema.sqlite.sql +104 -0
- data/db/schema.sqlserver.sql +113 -0
- data/db/schema_version +1 -0
- data/doc/README_FOR_APP +179 -0
- data/doc/mailinglist_classes description.txt +28 -0
- data/installer/rails_installer_defaults.yml +5 -0
- data/lib/daemonize.rb +56 -0
- data/lib/domain_system.rb +87 -0
- data/lib/gurgitate-rules.rb +69 -0
- data/lib/limitedfork.rb +66 -0
- data/lib/login_system.rb +87 -0
- data/public/.htaccess +40 -0
- data/public/404.html +8 -0
- data/public/500.html +8 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +24 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/images/rails.png +0 -0
- data/public/javascripts/application.js +2 -0
- data/public/javascripts/controls.js +815 -0
- data/public/javascripts/dragdrop.js +913 -0
- data/public/javascripts/effects.js +958 -0
- data/public/javascripts/prototype.js +2006 -0
- data/public/robots.txt +1 -0
- data/public/stylesheets/scaffold.css +74 -0
- data/public/stylesheets/trestle.css +74 -0
- data/script/about +3 -0
- data/script/breakpointer +3 -0
- data/script/console +3 -0
- data/script/destroy +3 -0
- data/script/fakedeliver +19 -0
- data/script/generate +3 -0
- data/script/performance/benchmarker +3 -0
- data/script/performance/profiler +3 -0
- data/script/plugin +3 -0
- data/script/process/reaper +3 -0
- data/script/process/spawner +3 -0
- data/script/runner +3 -0
- data/script/server +3 -0
- data/sugoi-mail.gemspec +36 -0
- data/test/fixtures/addresses.yml +70 -0
- data/test/fixtures/addresses_mailinglists.yml +20 -0
- data/test/fixtures/admin_messages.yml +65 -0
- data/test/fixtures/confirmationcodes.yml +13 -0
- data/test/fixtures/domains.yml +9 -0
- data/test/fixtures/mailinglist_classes.yml +45 -0
- data/test/fixtures/mailinglists.yml +80 -0
- data/test/fixtures/messages.yml +154 -0
- data/test/fixtures/proxy_links.yml +5 -0
- data/test/fixtures/users.yml +50 -0
- data/test/functional/domain_controller_test.rb +74 -0
- data/test/functional/mailservice_controller_test.rb +546 -0
- data/test/integration/test_soap.rb +413 -0
- data/test/integration/test_xmlrpc.rb +198 -0
- data/test/mocks/test/net-smtp-stub.rb +24 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/address_test.rb +44 -0
- data/test/unit/admin_message_test.rb +41 -0
- data/test/unit/confirmationcode_test.rb +64 -0
- data/test/unit/domain_test.rb +128 -0
- data/test/unit/mailinglist_class_test.rb +82 -0
- data/test/unit/mailinglist_test.rb +145 -0
- data/test/unit/message_test.rb +151 -0
- data/test/unit/user_test.rb +126 -0
- data/test/units.rb +4 -0
- data/vendor/plugins/active_command/init.rb +1 -0
- data/vendor/plugins/active_command/lib/active_command/request.rb +137 -0
- data/vendor/plugins/active_command/lib/active_command/response.rb +132 -0
- data/vendor/plugins/active_command/lib/active_command.rb +2 -0
- data/vendor/plugins/ar_fixtures/CHANGELOG +10 -0
- data/vendor/plugins/ar_fixtures/MIT-LICENSE +20 -0
- data/vendor/plugins/ar_fixtures/README +19 -0
- data/vendor/plugins/ar_fixtures/Rakefile +54 -0
- data/vendor/plugins/ar_fixtures/about.yml +7 -0
- data/vendor/plugins/ar_fixtures/init.rb +1 -0
- data/vendor/plugins/ar_fixtures/lib/ar_fixtures.rb +102 -0
- data/vendor/plugins/ar_fixtures/tasks/ar_fixtures.rake +39 -0
- data/vendor/plugins/ar_fixtures/test/ar_fixtures_test.rb +53 -0
- data/vendor/plugins/ar_fixtures/test/database.yml +18 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beer.rb +5 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beers.yml +9 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/beers_drunkards.yml +8 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/drunkard.rb +6 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/drunkards.yml +8 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/glass.rb +2 -0
- data/vendor/plugins/ar_fixtures/test/fixtures/glasses.yml +9 -0
- data/vendor/plugins/ar_fixtures/test/schema.rb +21 -0
- data/vendor/plugins/ar_fixtures/test/test_helper.rb +37 -0
- metadata +320 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
# The addresses table is where all the email addresses are stored, and
|
2
|
+
# Address is the interface to that.
|
3
|
+
#
|
4
|
+
# Address has the following data members:
|
5
|
+
# +id+:: The row ID in the database.
|
6
|
+
# +address+:: The actual email address
|
7
|
+
# +active+:: Whether the address is active or not. This should
|
8
|
+
# be reset to false upon a bounce, but currently
|
9
|
+
# isn't because there isn't a standard format for
|
10
|
+
# bounce messages and I haven't yet written the
|
11
|
+
# code to figure out which address I attempted to
|
12
|
+
# send mail to failed.
|
13
|
+
# +delivery_attempts+:: The number of delivery attempts. This is also
|
14
|
+
# related to bouncing, and is thus a stub.
|
15
|
+
# +bounces+:: The number of times email sent to this address
|
16
|
+
# has bounced. Like the other two bounce-related
|
17
|
+
# members, this is merely a stub.
|
18
|
+
#
|
19
|
+
# Address is also joined with other classes:
|
20
|
+
# Mailinglist:: The join between Mailinglist and Address reflects
|
21
|
+
# addresses are subscribed to a mailing list.
|
22
|
+
# User:: The join between Mailinglist and User reflects
|
23
|
+
# addresses that the User can send email to via the
|
24
|
+
# proxy in order to trigger email-address rewriting.
|
25
|
+
# Message:: Messages that were received from this Address.
|
26
|
+
|
27
|
+
class Address < ActiveRecord::Base
|
28
|
+
# The mailing lists that the address is a subscriber to
|
29
|
+
has_and_belongs_to_many :mailinglists
|
30
|
+
has_many :proxy_links
|
31
|
+
|
32
|
+
# The messages that were sent to this address
|
33
|
+
has_many :messages
|
34
|
+
|
35
|
+
# Parses the address string in +addrstr+ for an actual email address.
|
36
|
+
# This handles addresses of the forms:
|
37
|
+
#
|
38
|
+
# Dave Brown <dagbrown@mailman.jp>
|
39
|
+
# "Dave A. Brown" <dagbrown@mailman.jp>
|
40
|
+
# dagbrown@mailman.jp (Dave Brown)
|
41
|
+
# dagbrown@mailman.jp
|
42
|
+
#
|
43
|
+
# In other words, things that are likely to be in the From: and To: headers
|
44
|
+
# in an average email message.
|
45
|
+
#
|
46
|
+
# addrstr::
|
47
|
+
# The email address string.
|
48
|
+
def self.parse(addrstr) # not really OO, this
|
49
|
+
matches = /\s*([^@]+\@\S+) \((.*)\)/.match(addrstr)
|
50
|
+
if matches
|
51
|
+
return matches[1], matches[2]
|
52
|
+
end
|
53
|
+
|
54
|
+
matches = /(\".*\") \<([^@]+\@\S+)\>/.match(addrstr)
|
55
|
+
if matches
|
56
|
+
return matches[2], matches[1]
|
57
|
+
end
|
58
|
+
|
59
|
+
matches = /((?:\w|\s)*) \<([^@]+\@\S+)\>/.match(addrstr)
|
60
|
+
if matches
|
61
|
+
return matches[2], matches[1]
|
62
|
+
end
|
63
|
+
|
64
|
+
matches = /(\S+@\S+)/.match(addrstr)
|
65
|
+
if matches
|
66
|
+
return matches[1],nil
|
67
|
+
end
|
68
|
+
|
69
|
+
return addrstr, nil # and this is being probably needlessly optimistic
|
70
|
+
end
|
71
|
+
|
72
|
+
# If the address in +addrstr+ has a proxy address, returns the proxy
|
73
|
+
# address.
|
74
|
+
#
|
75
|
+
# addrstr::
|
76
|
+
# The address string to search for a proxy address for
|
77
|
+
def self.proxyaddress(addrstr)
|
78
|
+
a = Address.find_or_create_by_address(addrstr) and a.proxyaddress
|
79
|
+
end
|
80
|
+
|
81
|
+
# If the current address has a proxy address, return that.
|
82
|
+
def proxyaddress
|
83
|
+
user && user.address
|
84
|
+
end
|
85
|
+
|
86
|
+
def outside?
|
87
|
+
not user
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an anonymous proxy address for this user and a mailing list
|
91
|
+
def proxified ml
|
92
|
+
ProxyLink.find_or_create_by_address_id_and_mailinglist_id(
|
93
|
+
id, ml.id
|
94
|
+
).proxy_address
|
95
|
+
end
|
96
|
+
|
97
|
+
# If the address has a proxy address, then return the user that
|
98
|
+
# the proxy address belongs to.
|
99
|
+
def user
|
100
|
+
ml = mailinglists.find_all do |ml|
|
101
|
+
ml.id == ml.user.mailinglist_id && !ml.user.virtual?
|
102
|
+
end[0]
|
103
|
+
if ml then ml.user end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return a string representation of the address.
|
107
|
+
def to_s
|
108
|
+
address
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "net/smtp"
|
2
|
+
|
3
|
+
# Admin messages are the messages that are sent out when people are joining
|
4
|
+
# or leaving a mailing list.
|
5
|
+
class AdminMessage < ActiveRecord::Base
|
6
|
+
# The regular expression defining what templates look like. The
|
7
|
+
# templates are the things like {address} that are replaced with
|
8
|
+
# an actual address.
|
9
|
+
TemplateRegex = /\{(\w+)\}/
|
10
|
+
|
11
|
+
# Renders the current administration message, filling in the template with
|
12
|
+
# the values with the values in +template_values+. For example, if the
|
13
|
+
# message is:
|
14
|
+
#
|
15
|
+
# Thank you for subscribing to the "{mldesc}" mailing list.
|
16
|
+
#
|
17
|
+
# You might call #render as such:
|
18
|
+
#
|
19
|
+
# adminmsg.render :mldesc => "quick example"
|
20
|
+
#
|
21
|
+
# This would return:
|
22
|
+
#
|
23
|
+
# Thank you for subscribing to the "quick example" mailing list.
|
24
|
+
#
|
25
|
+
# template_values::
|
26
|
+
# A Hash containing as keys, template keys, and as values, the
|
27
|
+
# values to replace the template keys with.
|
28
|
+
def render template_values
|
29
|
+
template_values.each_key do |k|
|
30
|
+
template_values[k.to_s]=template_values[k]
|
31
|
+
end
|
32
|
+
self.message.gsub(TemplateRegex) do |text|
|
33
|
+
template_values[ text.match(TemplateRegex)[1] ]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sends out an admin message with with the +from+ address in the
|
38
|
+
# envelope "From" string, the +to+ address in the envelope "To" string,
|
39
|
+
# and with with +template_values+ expanded as in +render+.1
|
40
|
+
def send_mail from, to, template_values
|
41
|
+
Net::SMTP.start("localhost", 25) do |smtp|
|
42
|
+
smtp.send_message render(template_values), from, to
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "net/smtp"
|
2
|
+
|
3
|
+
# The Confirmation code is a sort of join table that links mailing lists
|
4
|
+
# with addresses, and also stores confirmation status.
|
5
|
+
class Confirmationcode < ActiveRecord::Base
|
6
|
+
belongs_to :mailinglist
|
7
|
+
belongs_to :address
|
8
|
+
|
9
|
+
# Creates a new confirmation code, and then freezes it so that it
|
10
|
+
# can't be modified.
|
11
|
+
def initialize
|
12
|
+
super
|
13
|
+
@attributes["code"]=("%16s" % rand(36**16).to_s(36)).gsub(/ /,"0")
|
14
|
+
@attributes["code"].freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
validates_presence_of :mailinglist
|
18
|
+
validates_presence_of :address
|
19
|
+
|
20
|
+
def code= _
|
21
|
+
raise TypeError, "can't modify code string"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Given a +mailinglist_id+ and an +address_id+, returns whether the
|
25
|
+
# address has been confirmed for that mailing list or not.
|
26
|
+
def self.confirmed? mailinglist_id, address_id
|
27
|
+
confirmation_record = find( :first, :conditions =>
|
28
|
+
[ 'mailinglist_id = ? and address_id = ?',
|
29
|
+
mailinglist_id, address_id ] )
|
30
|
+
if confirmation_record then
|
31
|
+
confirmation_record.confirmed
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Given a +mailinglist+, an +address+ and a confirmation code in
|
38
|
+
# +code+, it confirms the address in that mailing list if the
|
39
|
+
# confirmation code is correct.
|
40
|
+
#
|
41
|
+
# If the confirmation code is incorrect, returns false.
|
42
|
+
def self.confirm mailinglist, address, code
|
43
|
+
confirmation_record =
|
44
|
+
find(:first, :conditions => [
|
45
|
+
'mailinglist_id = ? and address_id = ? and code = ?',
|
46
|
+
mailinglist.id,
|
47
|
+
address.id,
|
48
|
+
code
|
49
|
+
])
|
50
|
+
|
51
|
+
if confirmation_record then
|
52
|
+
confirmation_record.confirmed = true
|
53
|
+
if confirmation_record.save then
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
# = Domain
|
4
|
+
#
|
5
|
+
# This is actually nothing more than a User class generated by the
|
6
|
+
# standard Rails login generator.
|
7
|
+
#
|
8
|
+
# Its data structure is very basic, as a result:
|
9
|
+
# name:: The domain's name.
|
10
|
+
# password:: A SHA1 hash of the domain's password.
|
11
|
+
#
|
12
|
+
# Its use as a login service is mainly for the purposes of the
|
13
|
+
# Mailservice web service--the middle layer (the SOAP client) logs into
|
14
|
+
# the domain, and then the users can log into _that_.
|
15
|
+
class Domain < ActiveRecord::Base
|
16
|
+
|
17
|
+
#----------------------------------------
|
18
|
+
# First of all, email address stuff
|
19
|
+
#----------------------------------------
|
20
|
+
has_many :users
|
21
|
+
|
22
|
+
# Please change the salt to something else,
|
23
|
+
# Every application should use a different one
|
24
|
+
@@salt = 'thuchpog10.?guf'
|
25
|
+
cattr_accessor :salt
|
26
|
+
|
27
|
+
# Authenticate a user.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
# @user = User.authenticate('bob', 'bobpass')
|
31
|
+
#
|
32
|
+
def self.authenticate(name, pass)
|
33
|
+
find_first(["name = ? AND password = ?", name, sha1(pass)])
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# Apply SHA1 encryption to the supplied password.
|
40
|
+
# We will additionally surround the password with a salt
|
41
|
+
# for additional security.
|
42
|
+
def self.sha1(pass)
|
43
|
+
Digest::SHA1.hexdigest("#{salt}--#{pass}--")
|
44
|
+
end
|
45
|
+
|
46
|
+
before_create :crypt_password
|
47
|
+
|
48
|
+
# Before saving the record to database we will crypt the password
|
49
|
+
# using SHA1.
|
50
|
+
# We never store the actual password in the DB.
|
51
|
+
def crypt_password
|
52
|
+
write_attribute "password", self.class.sha1(password)
|
53
|
+
end
|
54
|
+
|
55
|
+
before_update :crypt_unless_empty
|
56
|
+
|
57
|
+
# If the record is updated we will check if the password is empty.
|
58
|
+
# If its empty we assume that the user didn't want to change his
|
59
|
+
# password and just reset it to the old value.
|
60
|
+
def crypt_unless_empty
|
61
|
+
if password.empty?
|
62
|
+
user = self.class.find(self.id)
|
63
|
+
self.password = user.password
|
64
|
+
else
|
65
|
+
write_attribute "password", self.class.sha1(password)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
validates_uniqueness_of :name, :on => :create
|
70
|
+
|
71
|
+
validates_confirmation_of :password
|
72
|
+
validates_length_of :name, :within => 3..40
|
73
|
+
validates_length_of :password, :within => 5..40
|
74
|
+
validates_presence_of :name, :password, :password_confirmation,
|
75
|
+
:on => :create
|
76
|
+
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
# Mailing lists are what make Sugoi-Mail go. Which makes sense, I suppose,
|
2
|
+
# since Sugoi-Mail is a mailing list manager.
|
3
|
+
#
|
4
|
+
# A Mailing List has the following structure:
|
5
|
+
#
|
6
|
+
# (from the schema)
|
7
|
+
#
|
8
|
+
# name: The name of the mailing list. This will
|
9
|
+
# become the local part of the list's email
|
10
|
+
# address.
|
11
|
+
# description:: The description of the mailing list. This
|
12
|
+
# go into the "real name" portion of the mailing
|
13
|
+
# list's email address listed in the "From:"
|
14
|
+
# header in messages that are sent out to
|
15
|
+
# subscribers.
|
16
|
+
# user:: The Sugoi-Mail user who owns and manages the
|
17
|
+
# mailing list--this is a User object. The domain
|
18
|
+
# of the mailing list is the domain that the user
|
19
|
+
# is a member of.
|
20
|
+
# mailinglist_class:: The kind of mailing list that this is. This is
|
21
|
+
# a MailinglistClass object.
|
22
|
+
# welcome_admin_message:: The message that is sent out to a new member
|
23
|
+
# when a subscription is received. This is an
|
24
|
+
# AdminMessage object.
|
25
|
+
# confirmed_admin_message:: The message that is sent out to a new member
|
26
|
+
# when the subscription confirmation message
|
27
|
+
# is confirmed. This is also an AdminMessage
|
28
|
+
# object.
|
29
|
+
# sayonara_admin_message_id:: The final AdminMessage object (as of the moment
|
30
|
+
# anyway--couriermlm mailing lists have many more
|
31
|
+
# administrative messages, and I suspect I may
|
32
|
+
# have to implement all those at some point).
|
33
|
+
# This is the message that is sent out to a
|
34
|
+
# subscriber when she unsubscribes from the
|
35
|
+
# mailing list.
|
36
|
+
|
37
|
+
class Mailinglist < ActiveRecord::Base
|
38
|
+
belongs_to :user
|
39
|
+
has_and_belongs_to_many :addresses
|
40
|
+
has_many :messages
|
41
|
+
has_many :proxy_links
|
42
|
+
validates_each :name, :on => :create do |record, attr, value|
|
43
|
+
if record.user
|
44
|
+
if Mailinglist.find_by_sql(
|
45
|
+
['select * from mailinglists m, users u
|
46
|
+
where u.id = m.user_id
|
47
|
+
and u.domain_id = ?
|
48
|
+
and m.name = ?',
|
49
|
+
record.user.domain_id,
|
50
|
+
value ]).length > 0 then
|
51
|
+
record.errors.add("name",
|
52
|
+
"Address already exists: #{record.address}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
validates_associated :user, :on => :create
|
58
|
+
|
59
|
+
validates_presence_of :user
|
60
|
+
belongs_to :mailinglist_class
|
61
|
+
|
62
|
+
# Since rails's has_one interface apparently DOESN'T WORK, I
|
63
|
+
# have to do all these by hand.
|
64
|
+
|
65
|
+
%w{welcome confirmed sayonara}.each do |msgtype|
|
66
|
+
eval <<-EOT
|
67
|
+
def #{msgtype}_admin_message
|
68
|
+
AdminMessage.find #{msgtype}_admin_message_id
|
69
|
+
end
|
70
|
+
|
71
|
+
def #{msgtype}_admin_message=(msg)
|
72
|
+
self.#{msgtype}_admin_message_id = msg.id
|
73
|
+
end
|
74
|
+
EOT
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns true if this mailing list's address is the canonical address of a
|
78
|
+
# User.
|
79
|
+
def canonical_address?
|
80
|
+
id == user.mailinglist_id
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns true if the address +addr+ is subscribed to this mailing list.
|
84
|
+
def has_address? addr
|
85
|
+
expand_addresses.map { |a| a.address }.member? addr
|
86
|
+
end
|
87
|
+
|
88
|
+
def before_save
|
89
|
+
welcome_admin_message ||= AdminMessage.find 1
|
90
|
+
confirmed_admin_message ||= AdminMessage.find 2
|
91
|
+
sayonara_admin_message ||= AdminMessage.find 3
|
92
|
+
end
|
93
|
+
|
94
|
+
#------------------------------------------------------------------------
|
95
|
+
# Passthroughs to the mailinglist_class
|
96
|
+
#------------------------------------------------------------------------
|
97
|
+
[ :public?, :closed?, :moderated?,
|
98
|
+
:confirmation?, :joinable?, :archived?, :proxify? ].each do |m|
|
99
|
+
define_method m do
|
100
|
+
mailinglist_class.send m
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#------------------------------------------------------------------------
|
105
|
+
# These are all to make it work with rumble (as a replacement for
|
106
|
+
# rumble's mailing-list class).
|
107
|
+
#------------------------------------------------------------------------
|
108
|
+
|
109
|
+
def address; "#{name}@#{user.domain.name}"; end
|
110
|
+
def bounceaddress; "#{name}-bounce@#{user.domain.name}"; end
|
111
|
+
def requestaddress; "#{name}-request@#{user.domain.name}"; end
|
112
|
+
|
113
|
+
def self.find_by_address addr
|
114
|
+
if ml = find_by_basic_address(addr) then
|
115
|
+
return [ml,:mail]
|
116
|
+
elsif info = find_by_proxy_address(addr) then
|
117
|
+
ml, address_id = info
|
118
|
+
return [ml, :proxy, address_id]
|
119
|
+
elsif(ml = find_by_bounces_address(addr)) then
|
120
|
+
return [ml, :bounces]
|
121
|
+
elsif(ml = find_by_request_address(addr)) then
|
122
|
+
return [ml, :request]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.find_by_basic_address addr
|
127
|
+
(ml_name, domain_name) = addr.split "@"
|
128
|
+
domain=Domain.find_by_name domain_name
|
129
|
+
if domain
|
130
|
+
ml=Mailinglist.find_all_by_name(ml_name).find do |ml|
|
131
|
+
ml.domain.id == domain.id
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.find_by_proxy_address addr
|
137
|
+
if matchdata=addr.match(/-([0-9]+)\@/) then
|
138
|
+
proxy_id=matchdata[1].to_i
|
139
|
+
if ml=find_by_basic_address(addr.sub(/-[0-9]+\@/,"@")) then
|
140
|
+
begin
|
141
|
+
proxy=ProxyLink.find(proxy_id)
|
142
|
+
return ml, proxy.address
|
143
|
+
rescue
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.find_by_bounces_address bounces_addr
|
151
|
+
find_by_basic_address bounces_addr.sub(/-bounces\@/,"@")
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.find_by_request_address request_addr
|
155
|
+
find_by_basic_address request_addr.sub(/-request\@/,"@")
|
156
|
+
end
|
157
|
+
|
158
|
+
def confirmed? addr
|
159
|
+
if confirmation? then
|
160
|
+
Confirmationcode.confirmed? self.id, addr.id
|
161
|
+
else
|
162
|
+
true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def confirmed_addresses
|
167
|
+
addresses.find_all { |addr| confirmed? addr }
|
168
|
+
end
|
169
|
+
|
170
|
+
def pending_addresses
|
171
|
+
addresses.find_all { |addr| not confirmed? addr }
|
172
|
+
end
|
173
|
+
|
174
|
+
alias unconfirmed_addresses pending_addresses
|
175
|
+
|
176
|
+
def expand_addresses
|
177
|
+
address_list = addresses
|
178
|
+
expanded = false
|
179
|
+
until expanded
|
180
|
+
foundsome = false
|
181
|
+
new_address_list = address_list.map do |addr|
|
182
|
+
if ml = Mailinglist.find_by_address(addr.address) then
|
183
|
+
foundsome = true
|
184
|
+
ml[0].addresses
|
185
|
+
else
|
186
|
+
addr
|
187
|
+
end
|
188
|
+
end.flatten
|
189
|
+
expanded = ! foundsome
|
190
|
+
address_list = new_address_list
|
191
|
+
end
|
192
|
+
address_list
|
193
|
+
end
|
194
|
+
|
195
|
+
def domain; user.domain; end
|
196
|
+
|
197
|
+
def store_message message; messages << message; end
|
198
|
+
|
199
|
+
def send_message message
|
200
|
+
message.deliver
|
201
|
+
end
|
202
|
+
|
203
|
+
def subscribe(address,confirmationcode = nil)
|
204
|
+
addr=Address.find_or_create_by_address(address)
|
205
|
+
if confirmation?
|
206
|
+
if confirmed_addresses.member? addr
|
207
|
+
true
|
208
|
+
else
|
209
|
+
if confirmationcode then
|
210
|
+
confirm addr, confirmationcode
|
211
|
+
else
|
212
|
+
send_welcome_message(addr)
|
213
|
+
addresses << addr
|
214
|
+
end
|
215
|
+
end
|
216
|
+
else
|
217
|
+
if addresses.member? addr
|
218
|
+
true
|
219
|
+
else
|
220
|
+
addresses << addr
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def send_welcome_message(address)
|
226
|
+
if Address === address then
|
227
|
+
addr = address
|
228
|
+
else
|
229
|
+
addr=Address.find_or_create_by_address(address)
|
230
|
+
end
|
231
|
+
c=Confirmationcode.find_by_address_id_and_mailinglist_id addr.id, id
|
232
|
+
unless c
|
233
|
+
c=Confirmationcode.new
|
234
|
+
c.address_id=addr.id
|
235
|
+
c.mailinglist_id=id
|
236
|
+
c.save
|
237
|
+
end
|
238
|
+
|
239
|
+
unless c.confirmed?
|
240
|
+
welcome_admin_message.send_mail bounceaddress, addr.address,
|
241
|
+
:address => addr.address,
|
242
|
+
:command => "subscribe #{addr.address} #{c.code}",
|
243
|
+
:domain => domain.name,
|
244
|
+
:name => name,
|
245
|
+
:description => description,
|
246
|
+
:requestaddress => requestaddress
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def confirm address,confirmationcode
|
251
|
+
addr=nil
|
252
|
+
|
253
|
+
if Address === address
|
254
|
+
addr=address
|
255
|
+
else
|
256
|
+
addr=Address.find_by_address(address)
|
257
|
+
end
|
258
|
+
|
259
|
+
if addr then
|
260
|
+
if confirmation? then
|
261
|
+
if Confirmationcode.confirm(self, addr, confirmationcode) then
|
262
|
+
save
|
263
|
+
confirmed_admin_message.send_mail bounceaddress,
|
264
|
+
addr.address,
|
265
|
+
:address => addr.address,
|
266
|
+
:domain => domain.name,
|
267
|
+
:name => name,
|
268
|
+
:description => description,
|
269
|
+
:requestaddress => requestaddress
|
270
|
+
return true
|
271
|
+
else
|
272
|
+
return false
|
273
|
+
end
|
274
|
+
else
|
275
|
+
return true
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def unsubscribe(address, send_mail = true)
|
281
|
+
addr=nil
|
282
|
+
|
283
|
+
unless Address === address
|
284
|
+
addr=Address.find_by_address address
|
285
|
+
else
|
286
|
+
addr=address
|
287
|
+
end
|
288
|
+
|
289
|
+
if addresses.member? addr then
|
290
|
+
if send_mail
|
291
|
+
sayonara_admin_message.send_mail bounceaddress,
|
292
|
+
addr.address,
|
293
|
+
:address => addr.address,
|
294
|
+
:domain => domain.name,
|
295
|
+
:name => name,
|
296
|
+
:description => description,
|
297
|
+
:requestaddress => requestaddress
|
298
|
+
end
|
299
|
+
remove_addresses addr
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def handle_request requestmessage
|
304
|
+
subject=requestmessage.headers["Subject"][0].contents
|
305
|
+
matches=requestmessage.body.match(/^\W*subscribe\s+(\S+@[a-z0-9.-]+)
|
306
|
+
(?:\s+([a-z0-9]{16}))?$/x)
|
307
|
+
if matches then
|
308
|
+
# puts "Found subscription request: #{matches[0]}"
|
309
|
+
|
310
|
+
addresscandidate=matches[1]
|
311
|
+
confirmationcode=matches[2] # returns nil if there isn'tone
|
312
|
+
|
313
|
+
# Since you can have mailing lists that require confirmation, but
|
314
|
+
# they're not joinable, check for the confirmationcode case first.
|
315
|
+
if confirmationcode then
|
316
|
+
return subscribe(addresscandidate, confirmationcode)
|
317
|
+
else
|
318
|
+
if joinable? then
|
319
|
+
return subscribe(addresscandidate)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# TODO: actually implement confirmed unsubscribing down in the
|
325
|
+
# model. (This is, of course, to prevent pranksters from
|
326
|
+
# unsubscribing you from a mailing list behind your back.)
|
327
|
+
matches=requesmessage.body.match(/^\W*unsubscribe\s+(\S+@[a-z0-9.-]+)
|
328
|
+
(?:\s+([a-z0-9]{16}))?$/x)
|
329
|
+
if matches then
|
330
|
+
addresscandidate=matches[1]
|
331
|
+
confirmationcode=matches[2]
|
332
|
+
|
333
|
+
return unsubscribe(addresscandidate, confirmationcode)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# The mailing classes table is the table defining the behaviour of the
|
2
|
+
# mailing lists, and is, in a way, the heart of how sugoi-mail works.
|
3
|
+
#
|
4
|
+
# Rather than simply making a distinction between, say, email-address
|
5
|
+
# forwarding (the service offered by pobox.com) and mailing list
|
6
|
+
# management, everything in Sugoi-Mail is a mailing list. What _kind_
|
7
|
+
# of mailing list it is, is defined in the mailing list classes table.
|
8
|
+
#
|
9
|
+
# Delivery Attributes
|
10
|
+
# +public+:: Whether the list is open to the public or not.
|
11
|
+
# true: anyone can post to it (spammy!)
|
12
|
+
# false: only members can post to it <-- DEFAULT
|
13
|
+
# +closed+:: Determines who can send mail to the mailing list
|
14
|
+
# true: only the owner can post to it
|
15
|
+
# false: other people can post to it <-- DEFAULT
|
16
|
+
# +moderated+:: Determines whether mail goes straight to all addressees,
|
17
|
+
# or needs to be approved by the owner first.
|
18
|
+
# true: posted messages go to the owner first
|
19
|
+
# false: posted messages are broadcast <-- DEFAULT
|
20
|
+
#
|
21
|
+
# Address List Editing Attributes
|
22
|
+
# +confirmation+:: Whether subscription requests need to be confirmed,
|
23
|
+
# via an opt-in mechanism or not.
|
24
|
+
# true: subscriptions have to be confirmed <-- DEFAULT
|
25
|
+
# false: subscriptions happen automatically
|
26
|
+
# joinable:: Whether the general public can join a mailing list or not.
|
27
|
+
# If not, then only the owner can edit the address list.
|
28
|
+
# true: people can subscribe to the list at will <- DEFAULT
|
29
|
+
# false: only the owner can subscribe or unsubscribe people
|
30
|
+
#
|
31
|
+
# Other Attributes
|
32
|
+
# +archived+:: Whether messages are retained after delivery or not.
|
33
|
+
# true: Messages will be stored after they're delivered (useful for
|
34
|
+
# mailing list archives online)
|
35
|
+
# false: Messages will be discarded after delivery (probably what
|
36
|
+
# people who want to use sugoi-mail for forwarding want)
|
37
|
+
class MailinglistClass < ActiveRecord::Base
|
38
|
+
has_many :mailinglists
|
39
|
+
end
|