sugoi-mail 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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