1# Copyright (C) 2002-2018 by the Free Software Foundation, Inc.
2#
3# This program is free software; you can redistribute it and/or
4# modify it under the terms of the GNU General Public License
5# as published by the Free Software Foundation; either version 2
6# of the License, or (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17from email.Utils import parseaddr, formatdate
18
19from Mailman import mm_cfg
20from Mailman import Errors
21from Mailman import MemberAdaptor
22from Mailman import i18n
23
24def _(s): return s
25
26OVERVIEW = _("""
27    set ...
28        Set or view your membership options.
29
30        Use `set help' (without the quotes) to get a more detailed list of the
31        options you can change.
32
33        Use `set show' (without the quotes) to view your current option
34        settings.
35""")
36
37DETAILS = _("""
38    set help
39        Show this detailed help.
40
41    set show [address=<address>]
42        View your current option settings.  If you're posting from an address
43        other than your membership address, specify your membership address
44        with `address=<address>' (no brackets around the email address, and no
45        quotes!).
46
47    set authenticate <password> [address=<address>]
48        To set any of your options, you must include this command first, along
49        with your membership password.  If you're posting from an address
50        other than your membership address, specify your membership address
51        with `address=<address>' (no brackets around the email address, and no
52        quotes!).
53
54    set ack on
55    set ack off
56        When the `ack' option is turned on, you will receive an
57        acknowledgement message whenever you post a message to the list.
58
59    set digest plain
60    set digest mime
61    set digest off
62        When the `digest' option is turned off, you will receive postings
63        immediately when they are posted.  Use `set digest plain' if instead
64        you want to receive postings bundled into a plain text digest
65        (i.e. RFC 1153 digest).  Use `set digest mime' if instead you want to
66        receive postings bundled together into a MIME digest.
67
68    set delivery on
69    set delivery off
70        Turn delivery on or off.  This does not unsubscribe you, but instead
71        tells Mailman not to deliver messages to you for now.  This is useful
72        if you're going on vacation.  Be sure to use `set delivery on' when
73        you return from vacation!
74
75    set myposts on
76    set myposts off
77        Use `set myposts off' to not receive copies of messages you post to
78        the list.  This has no effect if you're receiving digests.
79
80    set hide on
81    set hide off
82        Use `set hide on' to conceal your email address when people request
83        the membership list.
84
85    set duplicates on
86    set duplicates off
87        Use `set duplicates off' if you want Mailman to not send you messages
88        if your address is explicitly mentioned in the To: or Cc: fields of
89        the message.  This can reduce the number of duplicate postings you
90        will receive.
91
92    set reminders on
93    set reminders off
94        Use `set reminders off' if you want to disable the monthly password
95        reminder for this mailing list.
96""")
97
98_ = i18n._
99
100STOP = 1
101
102
103
104def gethelp(mlist):
105    return _(OVERVIEW)
106
107
108
109class SetCommands:
110    def __init__(self):
111        self.__address = None
112        self.__authok = 0
113
114    def process(self, res, args):
115        if not args:
116            res.results.append(_(DETAILS))
117            return STOP
118        subcmd = args.pop(0)
119        methname = 'set_' + subcmd
120        method = getattr(self, methname, None)
121        if method is None:
122            res.results.append(_('Bad set command: %(subcmd)s'))
123            res.results.append(_(DETAILS))
124            return STOP
125        return method(res, args)
126
127    def set_help(self, res, args=1):
128        res.results.append(_(DETAILS))
129        if args:
130            return STOP
131
132    def _usage(self, res):
133        res.results.append(_('Usage:'))
134        return self.set_help(res)
135
136    def set_show(self, res, args):
137        mlist = res.mlist
138        if not args:
139            realname, address = parseaddr(res.msg['from'])
140        elif len(args) == 1 and args[0].startswith('address='):
141            # Send the results to the address, not the From: dude
142            address = args[0][8:]
143            res.returnaddr = address
144        else:
145            return self._usage(res)
146        if not mlist.isMember(address):
147            listname = mlist.real_name
148            res.results.append(
149                _('You are not a member of the %(listname)s mailing list'))
150            return STOP
151        res.results.append(_('Your current option settings:'))
152        opt = mlist.getMemberOption(address, mm_cfg.AcknowledgePosts)
153        onoff = opt and _('on') or _('off')
154        res.results.append(_('    ack %(onoff)s'))
155        # Digests are a special ternary value
156        digestsp = mlist.getMemberOption(address, mm_cfg.Digests)
157        if digestsp:
158            plainp = mlist.getMemberOption(address, mm_cfg.DisableMime)
159            if plainp:
160                res.results.append(_('    digest plain'))
161            else:
162                res.results.append(_('    digest mime'))
163        else:
164            res.results.append(_('    digest off'))
165        # If their membership is disabled, let them know why
166        status = mlist.getDeliveryStatus(address)
167        how = None
168        if status == MemberAdaptor.ENABLED:
169            status = _('delivery on')
170        elif status == MemberAdaptor.BYUSER:
171            status = _('delivery off')
172            how = _('by you')
173        elif status == MemberAdaptor.BYADMIN:
174            status = _('delivery off')
175            how = _('by the admin')
176        elif status == MemberAdaptor.BYBOUNCE:
177            status = _('delivery off')
178            how = _('due to bounces')
179        else:
180            assert status == MemberAdaptor.UNKNOWN
181            status = _('delivery off')
182            how = _('for unknown reasons')
183        changetime = mlist.getDeliveryStatusChangeTime(address)
184        if how and changetime > 0:
185            date = formatdate(changetime)
186            res.results.append(_('    %(status)s (%(how)s on %(date)s)'))
187        else:
188            res.results.append('    ' + status)
189        opt = mlist.getMemberOption(address, mm_cfg.DontReceiveOwnPosts)
190        # sense is reversed
191        onoff = (not opt) and _('on') or _('off')
192        res.results.append(_('    myposts %(onoff)s'))
193        opt = mlist.getMemberOption(address, mm_cfg.ConcealSubscription)
194        onoff = opt and _('on') or _('off')
195        res.results.append(_('    hide %(onoff)s'))
196        opt = mlist.getMemberOption(address, mm_cfg.DontReceiveDuplicates)
197        # sense is reversed
198        onoff = (not opt) and _('on') or _('off')
199        res.results.append(_('    duplicates %(onoff)s'))
200        opt = mlist.getMemberOption(address, mm_cfg.SuppressPasswordReminder)
201        # sense is reversed
202        onoff = (not opt) and _('on') or _('off')
203        res.results.append(_('    reminders %(onoff)s'))
204
205    def set_authenticate(self, res, args):
206        mlist = res.mlist
207        if len(args) == 1:
208            realname, address = parseaddr(res.msg['from'])
209            password = args[0]
210        elif len(args) == 2 and args[1].startswith('address='):
211            password = args[0]
212            address = args[1][8:]
213        else:
214            return self._usage(res)
215        # See if the password matches
216        if not mlist.isMember(address):
217            listname = mlist.real_name
218            res.results.append(
219                _('You are not a member of the %(listname)s mailing list'))
220            return STOP
221        if not mlist.Authenticate((mm_cfg.AuthUser,
222                                   mm_cfg.AuthListAdmin),
223                                  password, address):
224            res.results.append(_('You did not give the correct password'))
225            return STOP
226        self.__authok = 1
227        self.__address = address
228
229    def _status(self, res, arg):
230        status = arg.lower()
231        if status == 'on':
232            flag = 1
233        elif status == 'off':
234            flag = 0
235        else:
236            res.results.append(_('Bad argument: %(arg)s'))
237            self._usage(res)
238            return -1
239        # See if we're authenticated
240        if not self.__authok:
241            res.results.append(_('Not authenticated'))
242            self._usage(res)
243            return -1
244        return flag
245
246    def set_ack(self, res, args):
247        mlist = res.mlist
248        if len(args) <> 1:
249            return self._usage(res)
250        status = self._status(res, args[0])
251        if status < 0:
252            return STOP
253        mlist.setMemberOption(self.__address, mm_cfg.AcknowledgePosts, status)
254        res.results.append(_('ack option set'))
255
256    def set_digest(self, res, args):
257        mlist = res.mlist
258        if len(args) <> 1:
259            return self._usage(res)
260        if not self.__authok:
261            res.results.append(_('Not authenticated'))
262            self._usage(res)
263            return STOP
264        arg = args[0].lower()
265        if arg == 'off':
266            try:
267                mlist.setMemberOption(self.__address, mm_cfg.Digests, 0)
268            except Errors.AlreadyReceivingRegularDeliveries:
269                pass
270        elif arg == 'plain':
271            try:
272                mlist.setMemberOption(self.__address, mm_cfg.Digests, 1)
273            except Errors.AlreadyReceivingDigests:
274                pass
275            mlist.setMemberOption(self.__address, mm_cfg.DisableMime, 1)
276        elif arg == 'mime':
277            try:
278                mlist.setMemberOption(self.__address, mm_cfg.Digests, 1)
279            except Errors.AlreadyReceivingDigests:
280                pass
281            mlist.setMemberOption(self.__address, mm_cfg.DisableMime, 0)
282        else:
283            res.results.append(_('Bad argument: %(arg)s'))
284            self._usage(res)
285            return STOP
286        res.results.append(_('digest option set'))
287
288    def set_delivery(self, res, args):
289        mlist = res.mlist
290        if len(args) <> 1:
291            return self._usage(res)
292        status = self._status(res, args[0])
293        if status < 0:
294            return STOP
295        # Delivery status is handled differently than other options.  If
296        # status is true (set delivery on), then we enable delivery.
297        # Otherwise, we have to use the setDeliveryStatus() interface to
298        # specify that delivery was disabled by the user.
299        if status:
300            mlist.setDeliveryStatus(self.__address, MemberAdaptor.ENABLED)
301            res.results.append(_('delivery enabled'))
302        else:
303            mlist.setDeliveryStatus(self.__address, MemberAdaptor.BYUSER)
304            res.results.append(_('delivery disabled by user'))
305
306    def set_myposts(self, res, args):
307        mlist = res.mlist
308        if len(args) <> 1:
309            return self._usage(res)
310        status = self._status(res, args[0])
311        if status < 0:
312            return STOP
313        # sense is reversed
314        mlist.setMemberOption(self.__address, mm_cfg.DontReceiveOwnPosts,
315                              not status)
316        res.results.append(_('myposts option set'))
317
318    def set_hide(self, res, args):
319        mlist = res.mlist
320        if len(args) <> 1:
321            return self._usage(res)
322        status = self._status(res, args[0])
323        if status < 0:
324            return STOP
325        mlist.setMemberOption(self.__address, mm_cfg.ConcealSubscription,
326                              status)
327        res.results.append(_('hide option set'))
328
329    def set_duplicates(self, res, args):
330        mlist = res.mlist
331        if len(args) <> 1:
332            return self._usage(res)
333        status = self._status(res, args[0])
334        if status < 0:
335            return STOP
336        # sense is reversed
337        mlist.setMemberOption(self.__address, mm_cfg.DontReceiveDuplicates,
338                              not status)
339        res.results.append(_('duplicates option set'))
340
341    def set_reminders(self, res, args):
342        mlist = res.mlist
343        if len(args) <> 1:
344            return self._usage(res)
345        status = self._status(res, args[0])
346        if status < 0:
347            return STOP
348        # sense is reversed
349        mlist.setMemberOption(self.__address, mm_cfg.SuppressPasswordReminder,
350                              not status)
351        res.results.append(_('reminder option set'))
352
353
354
355def process(res, args):
356    # We need to keep some state between set commands
357    if not getattr(res, 'setstate', None):
358        res.setstate = SetCommands()
359    res.setstate.process(res, args)
360