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.
Files changed (182) hide show
  1. data/README +35 -0
  2. data/Rakefile +10 -0
  3. data/app/apis/mailservice_api.rb +178 -0
  4. data/app/controllers/.sugoi_admin_controller.rb.swp +0 -0
  5. data/app/controllers/account_controller.rb +39 -0
  6. data/app/controllers/address_controller.rb +49 -0
  7. data/app/controllers/application.rb +4 -0
  8. data/app/controllers/commandline_controller.rb +15 -0
  9. data/app/controllers/domain_controller.rb +37 -0
  10. data/app/controllers/mailinglist_controller.rb +51 -0
  11. data/app/controllers/mailservice_controller.rb +497 -0
  12. data/app/controllers/sugoi_admin_controller.rb +93 -0
  13. data/app/helpers/account_helper.rb +2 -0
  14. data/app/helpers/address_helper.rb +2 -0
  15. data/app/helpers/application_helper.rb +3 -0
  16. data/app/helpers/domain_helper.rb +2 -0
  17. data/app/helpers/mailservice_helper.rb +2 -0
  18. data/app/helpers/sugoi_admin_helper.rb +2 -0
  19. data/app/models/address.rb +110 -0
  20. data/app/models/admin_message.rb +45 -0
  21. data/app/models/confirmationcode.rb +59 -0
  22. data/app/models/domain.rb +76 -0
  23. data/app/models/mailinglist.rb +336 -0
  24. data/app/models/mailinglist_class.rb +39 -0
  25. data/app/models/message.rb +293 -0
  26. data/app/models/proxy_link.rb +25 -0
  27. data/app/models/user.rb +150 -0
  28. data/app/views/account/login.rhtml +22 -0
  29. data/app/views/account/logout.rhtml +10 -0
  30. data/app/views/account/signup.rhtml +17 -0
  31. data/app/views/account/welcome.rhtml +13 -0
  32. data/app/views/address/_form.rhtml +7 -0
  33. data/app/views/address/edit.rhtml +10 -0
  34. data/app/views/address/list.rhtml +27 -0
  35. data/app/views/address/new.rhtml +8 -0
  36. data/app/views/address/show.rhtml +8 -0
  37. data/app/views/domain/login.rhtml +22 -0
  38. data/app/views/domain/logout.rhtml +10 -0
  39. data/app/views/domain/signup.rhtml +17 -0
  40. data/app/views/domain/welcome.rhtml +13 -0
  41. data/app/views/layouts/address.rhtml +13 -0
  42. data/app/views/layouts/scaffold.rhtml +13 -0
  43. data/app/views/mailinglist/_form.rhtml +28 -0
  44. data/app/views/mailinglist/edit.rhtml +10 -0
  45. data/app/views/mailinglist/list.rhtml +27 -0
  46. data/app/views/mailinglist/new.rhtml +8 -0
  47. data/app/views/mailinglist/show.rhtml +16 -0
  48. data/app/views/sugoi_admin/create_domain.rhtml +1 -0
  49. data/app/views/sugoi_admin/list_addresses.rhtml +1 -0
  50. data/app/views/sugoi_admin/list_domains.rhtml +2 -0
  51. data/app/views/sugoi_admin/list_mailinglists.rhtml +1 -0
  52. data/bin/mailc +32 -0
  53. data/bin/maild +133 -0
  54. data/bin/sugoi-admin +21 -0
  55. data/bin/sugoi-mail +20 -0
  56. data/config/boot.rb +44 -0
  57. data/config/environment.rb +54 -0
  58. data/config/environments/bench.rb +21 -0
  59. data/config/environments/coverage.rb +21 -0
  60. data/config/environments/development.rb +21 -0
  61. data/config/environments/production.rb +18 -0
  62. data/config/environments/test.rb +19 -0
  63. data/config/lighttpd.conf +46 -0
  64. data/config/routes.rb +29 -0
  65. data/db/migrate/001_mailproxy.rb +7 -0
  66. data/db/migrate/002_create_users.rb +13 -0
  67. data/db/migrate/003_create_mailinglists.rb +13 -0
  68. data/db/migrate/004_create_addresses.rb +12 -0
  69. data/db/migrate/005_create_addresses_mailinglists.rb +13 -0
  70. data/db/migrate/006_alter_mailinglists.rb +9 -0
  71. data/db/migrate/007_create_messages.rb +25 -0
  72. data/db/migrate/008_add_mailinglistid_to_users.rb +14 -0
  73. data/db/migrate/009_add_domainadmin_to_users.rb +9 -0
  74. data/db/migrate/010_add_domain_to_users.rb +16 -0
  75. data/db/migrate/011_add_active_to_addresses.rb +14 -0
  76. data/db/migrate/012_create_confirmationcodes.rb +14 -0
  77. data/db/migrate/013_add_description_to_mailinglists.rb +9 -0
  78. data/db/migrate/014_create_admin_messages.rb +69 -0
  79. data/db/migrate/015_add_messages_to_mailinglists.rb +26 -0
  80. data/db/migrate/016_add_mailinglist_admin_to_users.rb +9 -0
  81. data/db/migrate/017_add_mailinglist_types.rb +94 -0
  82. data/db/migrate/018_add_bounciness_to_addresses.rb +20 -0
  83. data/db/migrate/019_add_archived_to_mailinglist_classes.rb +25 -0
  84. data/db/migrate/020_add_envelope_data_to_messages.rb +11 -0
  85. data/db/migrate/021_add_addresses_users.rb +14 -0
  86. data/db/migrate/022_add_virtual_to_users.rb +14 -0
  87. data/db/migrate/023_drop_openposting_from_mailinglists.rb +9 -0
  88. data/db/migrate/024_add_proxy_links.rb +21 -0
  89. data/db/migrate/025_add_proxify_to_mailinglist_classes.rb +25 -0
  90. data/db/schema.mysql.sql +104 -0
  91. data/db/schema.postgresql.sql +104 -0
  92. data/db/schema.rb +85 -0
  93. data/db/schema.sqlite.sql +104 -0
  94. data/db/schema.sqlserver.sql +113 -0
  95. data/db/schema_version +1 -0
  96. data/doc/README_FOR_APP +179 -0
  97. data/doc/mailinglist_classes description.txt +28 -0
  98. data/installer/rails_installer_defaults.yml +5 -0
  99. data/lib/daemonize.rb +56 -0
  100. data/lib/domain_system.rb +87 -0
  101. data/lib/gurgitate-rules.rb +69 -0
  102. data/lib/limitedfork.rb +66 -0
  103. data/lib/login_system.rb +87 -0
  104. data/public/.htaccess +40 -0
  105. data/public/404.html +8 -0
  106. data/public/500.html +8 -0
  107. data/public/dispatch.cgi +10 -0
  108. data/public/dispatch.fcgi +24 -0
  109. data/public/dispatch.rb +10 -0
  110. data/public/favicon.ico +0 -0
  111. data/public/images/rails.png +0 -0
  112. data/public/javascripts/application.js +2 -0
  113. data/public/javascripts/controls.js +815 -0
  114. data/public/javascripts/dragdrop.js +913 -0
  115. data/public/javascripts/effects.js +958 -0
  116. data/public/javascripts/prototype.js +2006 -0
  117. data/public/robots.txt +1 -0
  118. data/public/stylesheets/scaffold.css +74 -0
  119. data/public/stylesheets/trestle.css +74 -0
  120. data/script/about +3 -0
  121. data/script/breakpointer +3 -0
  122. data/script/console +3 -0
  123. data/script/destroy +3 -0
  124. data/script/fakedeliver +19 -0
  125. data/script/generate +3 -0
  126. data/script/performance/benchmarker +3 -0
  127. data/script/performance/profiler +3 -0
  128. data/script/plugin +3 -0
  129. data/script/process/reaper +3 -0
  130. data/script/process/spawner +3 -0
  131. data/script/runner +3 -0
  132. data/script/server +3 -0
  133. data/sugoi-mail.gemspec +36 -0
  134. data/test/fixtures/addresses.yml +70 -0
  135. data/test/fixtures/addresses_mailinglists.yml +20 -0
  136. data/test/fixtures/admin_messages.yml +65 -0
  137. data/test/fixtures/confirmationcodes.yml +13 -0
  138. data/test/fixtures/domains.yml +9 -0
  139. data/test/fixtures/mailinglist_classes.yml +45 -0
  140. data/test/fixtures/mailinglists.yml +80 -0
  141. data/test/fixtures/messages.yml +154 -0
  142. data/test/fixtures/proxy_links.yml +5 -0
  143. data/test/fixtures/users.yml +50 -0
  144. data/test/functional/domain_controller_test.rb +74 -0
  145. data/test/functional/mailservice_controller_test.rb +546 -0
  146. data/test/integration/test_soap.rb +413 -0
  147. data/test/integration/test_xmlrpc.rb +198 -0
  148. data/test/mocks/test/net-smtp-stub.rb +24 -0
  149. data/test/test_helper.rb +28 -0
  150. data/test/unit/address_test.rb +44 -0
  151. data/test/unit/admin_message_test.rb +41 -0
  152. data/test/unit/confirmationcode_test.rb +64 -0
  153. data/test/unit/domain_test.rb +128 -0
  154. data/test/unit/mailinglist_class_test.rb +82 -0
  155. data/test/unit/mailinglist_test.rb +145 -0
  156. data/test/unit/message_test.rb +151 -0
  157. data/test/unit/user_test.rb +126 -0
  158. data/test/units.rb +4 -0
  159. data/vendor/plugins/active_command/init.rb +1 -0
  160. data/vendor/plugins/active_command/lib/active_command/request.rb +137 -0
  161. data/vendor/plugins/active_command/lib/active_command/response.rb +132 -0
  162. data/vendor/plugins/active_command/lib/active_command.rb +2 -0
  163. data/vendor/plugins/ar_fixtures/CHANGELOG +10 -0
  164. data/vendor/plugins/ar_fixtures/MIT-LICENSE +20 -0
  165. data/vendor/plugins/ar_fixtures/README +19 -0
  166. data/vendor/plugins/ar_fixtures/Rakefile +54 -0
  167. data/vendor/plugins/ar_fixtures/about.yml +7 -0
  168. data/vendor/plugins/ar_fixtures/init.rb +1 -0
  169. data/vendor/plugins/ar_fixtures/lib/ar_fixtures.rb +102 -0
  170. data/vendor/plugins/ar_fixtures/tasks/ar_fixtures.rake +39 -0
  171. data/vendor/plugins/ar_fixtures/test/ar_fixtures_test.rb +53 -0
  172. data/vendor/plugins/ar_fixtures/test/database.yml +18 -0
  173. data/vendor/plugins/ar_fixtures/test/fixtures/beer.rb +5 -0
  174. data/vendor/plugins/ar_fixtures/test/fixtures/beers.yml +9 -0
  175. data/vendor/plugins/ar_fixtures/test/fixtures/beers_drunkards.yml +8 -0
  176. data/vendor/plugins/ar_fixtures/test/fixtures/drunkard.rb +6 -0
  177. data/vendor/plugins/ar_fixtures/test/fixtures/drunkards.yml +8 -0
  178. data/vendor/plugins/ar_fixtures/test/fixtures/glass.rb +2 -0
  179. data/vendor/plugins/ar_fixtures/test/fixtures/glasses.yml +9 -0
  180. data/vendor/plugins/ar_fixtures/test/schema.rb +21 -0
  181. data/vendor/plugins/ar_fixtures/test/test_helper.rb +37 -0
  182. 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