1 # Copyright (C) 2002-2016 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """The email commands 'join' and 'subscribe'."""
20 from email
.utils
import formataddr
, parseaddr
21 from mailman
import public
22 from mailman
.core
.i18n
import _
23 from mailman
.interfaces
.command
import ContinueProcessing
, IEmailCommand
24 from mailman
.interfaces
.member
import DeliveryMode
, MemberRole
25 from mailman
.interfaces
.registrar
import IRegistrar
26 from mailman
.interfaces
.subscriptions
import ISubscriptionService
27 from mailman
.interfaces
.usermanager
import IUserManager
28 from zope
.component
import getUtility
29 from zope
.interface
import implementer
32 def match_subscriber(email
, display_name
):
33 # Return something matching the email which should be used as the
34 # subscriber by the IRegistrar interface.
35 manager
= getUtility(IUserManager
)
36 # Is there a user with a preferred address matching the email?
37 user
= manager
.get_user(email
)
39 preferred
= user
.preferred_address
40 if preferred
is not None and preferred
.email
== email
.lower():
42 # Is there an address matching the email?
43 address
= manager
.get_address(email
)
44 if address
is not None:
46 # Make a new user and subscribe their first (and only) address. We can't
47 # make the first address their preferred address because it hasn't been
49 user
= manager
.make_user(email
, display_name
)
50 return list(user
.addresses
)[0]
54 @implementer(IEmailCommand
)
56 """The email 'join' command."""
59 # XXX 2012-02-29 BAW: DeliveryMode.summary is not yet supported.
60 argument_description
= '[digest=<no|mime|plain>]'
62 You will be asked to confirm your subscription request and you may be issued a
65 By using the 'digest' option, you can specify whether you want digest delivery
66 or not. If not specified, the mailing list's default delivery mode will be
69 short_description
= _('Join this mailing list.')
71 def process(self
, mlist
, msg
, msgdata
, arguments
, results
):
72 """See `IEmailCommand`."""
73 # Parse the arguments.
74 delivery_mode
= self
._parse
_arguments
(arguments
, results
)
75 if delivery_mode
is ContinueProcessing
.no
:
76 return ContinueProcessing
.no
77 display_name
, email
= parseaddr(msg
['from'])
78 # Address could be None or the empty string.
82 print(_('$self.name: No valid address found to subscribe'),
84 return ContinueProcessing
.no
85 if isinstance(email
, bytes
):
86 email
= email
.decode('ascii')
87 # Have we already seen one join request from this user during the
88 # processing of this email?
89 joins
= getattr(results
, 'joins', set())
91 # Do not register this join.
92 return ContinueProcessing
.yes
95 person
= formataddr((display_name
, email
)) # noqa
96 # Is this person already a member of the list? Search for all
97 # matching memberships.
98 members
= getUtility(ISubscriptionService
).find_members(
99 email
, mlist
.list_id
, MemberRole
.member
)
101 print(_('$person is already a member'), file=results
)
102 return ContinueProcessing
.yes
103 subscriber
= match_subscriber(email
, display_name
)
104 IRegistrar(mlist
).register(subscriber
)
105 print(_('Confirmation email sent to $person'), file=results
)
106 return ContinueProcessing
.yes
108 def _parse_arguments(self
, arguments
, results
):
109 """Parse command arguments.
111 :param arguments: The sequences of arguments as given to the
113 :param results: The results object.
114 :return: The delivery mode, None, or ContinueProcessing.no on error.
116 mode
= DeliveryMode
.regular
117 for argument
in arguments
:
118 parts
= argument
.split('=', 1)
119 if len(parts
) != 2 or parts
[0] != 'digest':
120 print(self
.name
, _('bad argument: $argument'),
122 return ContinueProcessing
.no
124 'no': DeliveryMode
.regular
,
125 'plain': DeliveryMode
.plaintext_digests
,
126 'mime': DeliveryMode
.mime_digests
,
129 print(self
.name
, _('bad argument: $argument'),
131 return ContinueProcessing
.no
136 class Subscribe(Join
):
137 """The email 'subscribe' command (an alias for 'join')."""
140 description
= _("An alias for 'join'.")
141 short_description
= description
145 @implementer(IEmailCommand
)
147 """The email 'leave' command."""
150 argument_description
= ''
151 description
= _("""Leave this mailing list.
153 You may be asked to confirm your request.""")
154 short_description
= _('Leave this mailing list.')
156 def process(self
, mlist
, msg
, msgdata
, arguments
, results
):
157 """See `IEmailCommand`."""
160 print(_('$self.name: No valid email address found to unsubscribe'),
162 return ContinueProcessing
.no
163 user_manager
= getUtility(IUserManager
)
164 user
= user_manager
.get_user(email
)
166 print(_('No registered user for email address: $email'),
168 return ContinueProcessing
.no
169 # The address that the -leave command was sent from, must be verified.
170 # Otherwise you could link a bogus address to anyone's account, and
171 # then send a leave command from that address.
172 if user_manager
.get_address(email
).verified_on
is None:
173 print(_('Invalid or unverified email address: $email'),
175 return ContinueProcessing
.no
176 for user_address
in user
.addresses
:
177 # Only recognize verified addresses.
178 if user_address
.verified_on
is None:
180 member
= mlist
.members
.get_member(user_address
.email
)
181 if member
is not None:
184 # None of the user's addresses are subscribed to this mailing list.
186 '$self.name: $email is not a member of $mlist.fqdn_listname'),
188 return ContinueProcessing
.no
190 person
= formataddr((user
.display_name
, email
)) # noqa
191 print(_('$person left $mlist.fqdn_listname'), file=results
)
192 return ContinueProcessing
.yes
196 class Unsubscribe(Leave
):
197 """The email 'unsubscribe' command (an alias for 'leave')."""
200 description
= _("An alias for 'leave'.")
201 short_description
= description