Apply 01_defaults.debian.patch
[mspang/vmailman.git] / Mailman / OldStyleMemberships.py
1 # Copyright (C) 2001-2003 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
17 """Old style Mailman membership adaptor.
18
19 This adaptor gets and sets member information on the MailList object given to
20 the constructor.  It also equates member keys and lower-cased email addresses,
21 i.e. KEY is LCE.
22
23 This is the adaptor used by default in Mailman 2.1.
24 """
25
26 import time
27 from types import StringType
28
29 from Mailman import mm_cfg
30 from Mailman import Utils
31 from Mailman import Errors
32 from Mailman import MemberAdaptor
33
34 ISREGULAR = 1
35 ISDIGEST = 2
36
37 # XXX check for bare access to mlist.members, mlist.digest_members,
38 # mlist.user_options, mlist.passwords, mlist.topics_userinterest
39
40 # XXX Fix Errors.MMAlreadyAMember and Errors.NotAMember
41 # Actually, fix /all/ errors
42
43
44 \f
45 class OldStyleMemberships(MemberAdaptor.MemberAdaptor):
46     def __init__(self, mlist):
47         self.__mlist = mlist
48
49     #
50     # Read interface
51     #
52     def getMembers(self):
53         return self.__mlist.members.keys() + self.__mlist.digest_members.keys()
54
55     def getRegularMemberKeys(self):
56         return self.__mlist.members.keys()
57
58     def getDigestMemberKeys(self):
59         return self.__mlist.digest_members.keys()
60
61     def __get_cp_member(self, member):
62         lcmember = member.lower()
63         missing = []
64         val = self.__mlist.members.get(lcmember, missing)
65         if val is not missing:
66             if isinstance(val, StringType):
67                 return val, ISREGULAR
68             else:
69                 return lcmember, ISREGULAR
70         val = self.__mlist.digest_members.get(lcmember, missing)
71         if val is not missing:
72             if isinstance(val, StringType):
73                 return val, ISDIGEST
74             else:
75                 return lcmember, ISDIGEST
76         return None, None
77
78     def isMember(self, member):
79         cpaddr, where = self.__get_cp_member(member)
80         if cpaddr is not None:
81             return 1
82         return 0
83
84     def getMemberKey(self, member):
85         cpaddr, where = self.__get_cp_member(member)
86         if cpaddr is None:
87             raise Errors.NotAMemberError, member
88         return member.lower()
89
90     def getMemberCPAddress(self, member):
91         cpaddr, where = self.__get_cp_member(member)
92         if cpaddr is None:
93             raise Errors.NotAMemberError, member
94         return cpaddr
95
96     def getMemberCPAddresses(self, members):
97         return [self.__get_cp_member(member)[0] for member in members]
98
99     def getMemberPassword(self, member):
100         secret = self.__mlist.passwords.get(member.lower())
101         if secret is None:
102             raise Errors.NotAMemberError, member
103         return secret
104
105     def authenticateMember(self, member, response):
106         secret = self.getMemberPassword(member)
107         if secret == response:
108             return secret
109         return 0
110
111     def __assertIsMember(self, member):
112         if not self.isMember(member):
113             raise Errors.NotAMemberError, member
114
115     def getMemberLanguage(self, member):
116         lang = self.__mlist.language.get(
117             member.lower(), self.__mlist.preferred_language)
118         if lang in self.__mlist.GetAvailableLanguages():
119             return lang
120         return self.__mlist.preferred_language
121
122     def getMemberOption(self, member, flag):
123         self.__assertIsMember(member)
124         if flag == mm_cfg.Digests:
125             cpaddr, where = self.__get_cp_member(member)
126             return where == ISDIGEST
127         option = self.__mlist.user_options.get(member.lower(), 0)
128         return not not (option & flag)
129
130     def getMemberName(self, member):
131         self.__assertIsMember(member)
132         return self.__mlist.usernames.get(member.lower())
133
134     def getMemberTopics(self, member):
135         self.__assertIsMember(member)
136         return self.__mlist.topics_userinterest.get(member.lower(), [])
137
138     def getDeliveryStatus(self, member):
139         self.__assertIsMember(member)
140         return self.__mlist.delivery_status.get(
141             member.lower(),
142             # Values are tuples, so the default should also be a tuple.  The
143             # second item will be ignored.
144             (MemberAdaptor.ENABLED, 0))[0]
145
146     def getDeliveryStatusChangeTime(self, member):
147         self.__assertIsMember(member)
148         return self.__mlist.delivery_status.get(
149             member.lower(),
150             # Values are tuples, so the default should also be a tuple.  The
151             # second item will be ignored.
152             (MemberAdaptor.ENABLED, 0))[1]
153
154     def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
155                                                MemberAdaptor.BYUSER,
156                                                MemberAdaptor.BYADMIN,
157                                                MemberAdaptor.BYBOUNCE)):
158         return [member for member in self.getMembers()
159                 if self.getDeliveryStatus(member) in status]
160
161     def getBouncingMembers(self):
162         return [member.lower() for member in self.__mlist.bounce_info.keys()]
163
164     def getBounceInfo(self, member):
165         self.__assertIsMember(member)
166         return self.__mlist.bounce_info.get(member.lower())
167
168     #
169     # Write interface
170     #
171     def addNewMember(self, member, **kws):
172         assert self.__mlist.Locked()
173         # Make sure this address isn't already a member
174         if self.isMember(member):
175             raise Errors.MMAlreadyAMember, member
176         # Parse the keywords
177         digest = 0
178         password = Utils.MakeRandomPassword()
179         language = self.__mlist.preferred_language
180         realname = None
181         if kws.has_key('digest'):
182             digest = kws['digest']
183             del kws['digest']
184         if kws.has_key('password'):
185             password = kws['password']
186             del kws['password']
187         if kws.has_key('language'):
188             language = kws['language']
189             del kws['language']
190         if kws.has_key('realname'):
191             realname = kws['realname']
192             del kws['realname']
193         # Assert that no other keywords are present
194         if kws:
195             raise ValueError, kws.keys()
196         # If the localpart has uppercase letters in it, then the value in the
197         # members (or digest_members) dict is the case preserved address.
198         # Otherwise the value is 0.  Note that the case of the domain part is
199         # of course ignored.
200         if Utils.LCDomain(member) == member.lower():
201             value = 0
202         else:
203             value = member
204             member = member.lower()
205         if digest:
206             self.__mlist.digest_members[member] = value
207         else:
208             self.__mlist.members[member] = value
209         self.setMemberPassword(member, password)
210
211         self.setMemberLanguage(member, language)
212         if realname:
213             self.setMemberName(member, realname)
214         # Set the member's default set of options
215         if self.__mlist.new_member_options:
216             self.__mlist.user_options[member] = self.__mlist.new_member_options
217
218     def removeMember(self, member):
219         assert self.__mlist.Locked()
220         self.__assertIsMember(member)
221         # Delete the appropriate entries from the various MailList attributes.
222         # Remember that not all of them will have an entry (only those with
223         # values different than the default).
224         memberkey = member.lower()
225         for attr in ('passwords', 'user_options', 'members', 'digest_members',
226                      'language',  'topics_userinterest',     'usernames',
227                      'bounce_info', 'delivery_status',
228                      ):
229             dict = getattr(self.__mlist, attr)
230             if dict.has_key(memberkey):
231                 del dict[memberkey]
232
233     def changeMemberAddress(self, member, newaddress, nodelete=0):
234         assert self.__mlist.Locked()
235         # Make sure the old address is a member.  Assertions that the new
236         # address is not already a member is done by addNewMember() below.
237         self.__assertIsMember(member)
238         # Get the old values
239         memberkey = member.lower()
240         fullname = self.getMemberName(memberkey)
241         flags = self.__mlist.user_options.get(memberkey, 0)
242         digestsp = self.getMemberOption(memberkey, mm_cfg.Digests)
243         password = self.__mlist.passwords.get(memberkey,
244                                               Utils.MakeRandomPassword())
245         lang = self.getMemberLanguage(memberkey)
246         # First, possibly delete the old member
247         if not nodelete:
248             self.removeMember(memberkey)
249         # Now, add the new member
250         self.addNewMember(newaddress, realname=fullname, digest=digestsp,
251                           password=password, language=lang)
252         # Set the entire options bitfield
253         if flags:
254             self.__mlist.user_options[newaddress.lower()] = flags
255
256     def setMemberPassword(self, memberkey, password):
257         assert self.__mlist.Locked()
258         self.__assertIsMember(memberkey)
259         self.__mlist.passwords[memberkey.lower()] = password
260
261     def setMemberLanguage(self, memberkey, language):
262         assert self.__mlist.Locked()
263         self.__assertIsMember(memberkey)
264         self.__mlist.language[memberkey.lower()] = language
265
266     def setMemberOption(self, member, flag, value):
267         assert self.__mlist.Locked()
268         self.__assertIsMember(member)
269         memberkey = member.lower()
270         # There's one extra gotcha we have to deal with.  If the user is
271         # toggling the Digests flag, then we need to move their entry from
272         # mlist.members to mlist.digest_members or vice versa.  Blarg.  Do
273         # this before the flag setting below in case it fails.
274         if flag == mm_cfg.Digests:
275             if value:
276                 # Be sure the list supports digest delivery
277                 if not self.__mlist.digestable:
278                     raise Errors.CantDigestError
279                 # The user is turning on digest mode
280                 if self.__mlist.digest_members.has_key(memberkey):
281                     raise Errors.AlreadyReceivingDigests, member
282                 cpuser = self.__mlist.members.get(memberkey)
283                 if cpuser is None:
284                     raise Errors.NotAMemberError, member
285                 del self.__mlist.members[memberkey]
286                 self.__mlist.digest_members[memberkey] = cpuser
287             else:
288                 # Be sure the list supports regular delivery
289                 if not self.__mlist.nondigestable:
290                     raise Errors.MustDigestError
291                 # The user is turning off digest mode
292                 if self.__mlist.members.has_key(memberkey):
293                     raise Errors.AlreadyReceivingRegularDeliveries, member
294                 cpuser = self.__mlist.digest_members.get(memberkey)
295                 if cpuser is None:
296                     raise Errors.NotAMemberError, member
297                 del self.__mlist.digest_members[memberkey]
298                 self.__mlist.members[memberkey] = cpuser
299                 # When toggling off digest delivery, we want to be sure to set
300                 # things up so that the user receives one last digest,
301                 # otherwise they may lose some email
302                 self.__mlist.one_last_digest[memberkey] = cpuser
303             # We don't need to touch user_options because the digest state
304             # isn't kept as a bitfield flag.
305             return
306         # This is a bit kludgey because the semantics are that if the user has
307         # no options set (i.e. the value would be 0), then they have no entry
308         # in the user_options dict.  We use setdefault() here, and then del
309         # the entry below just to make things (questionably) cleaner.
310         self.__mlist.user_options.setdefault(memberkey, 0)
311         if value:
312             self.__mlist.user_options[memberkey] |= flag
313         else:
314             self.__mlist.user_options[memberkey] &= ~flag
315         if not self.__mlist.user_options[memberkey]:
316             del self.__mlist.user_options[memberkey]
317
318     def setMemberName(self, member, realname):
319         assert self.__mlist.Locked()
320         self.__assertIsMember(member)
321         self.__mlist.usernames[member.lower()] = realname
322
323     def setMemberTopics(self, member, topics):
324         assert self.__mlist.Locked()
325         self.__assertIsMember(member)
326         memberkey = member.lower()
327         if topics:
328             self.__mlist.topics_userinterest[memberkey] = topics
329         # if topics is empty, then delete the entry in this dictionary
330         elif self.__mlist.topics_userinterest.has_key(memberkey):
331             del self.__mlist.topics_userinterest[memberkey]
332
333     def setDeliveryStatus(self, member, status):
334         assert status in (MemberAdaptor.ENABLED,  MemberAdaptor.UNKNOWN,
335                           MemberAdaptor.BYUSER,   MemberAdaptor.BYADMIN,
336                           MemberAdaptor.BYBOUNCE)
337         assert self.__mlist.Locked()
338         self.__assertIsMember(member)
339         member = member.lower()
340         if status == MemberAdaptor.ENABLED:
341             # Enable by resetting their bounce info.
342             self.setBounceInfo(member, None)
343         else:
344             self.__mlist.delivery_status[member] = (status, time.time())
345
346     def setBounceInfo(self, member, info):
347         assert self.__mlist.Locked()
348         self.__assertIsMember(member)
349         member = member.lower()
350         if info is None:
351             if self.__mlist.bounce_info.has_key(member):
352                 del self.__mlist.bounce_info[member]
353             if self.__mlist.delivery_status.has_key(member):
354                 del self.__mlist.delivery_status[member]
355         else:
356             self.__mlist.bounce_info[member] = info