1779
by Mark Sapiro
Bump copyright dates. |
1 |
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
|
1
by
This commit was manufactured by cvs2svn to create branch |
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.
|
|
20
by bwarsaw
Backporting from the trunk. |
7 |
#
|
1
by
This commit was manufactured by cvs2svn to create branch |
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.
|
|
20
by bwarsaw
Backporting from the trunk. |
12 |
#
|
1
by
This commit was manufactured by cvs2svn to create branch |
13 |
# You should have received a copy of the GNU General Public License
|
20
by bwarsaw
Backporting from the trunk. |
14 |
# along with this program; if not, write to the Free Software
|
749
by tkikuchi
FSF office has moved to 51 Franklin Street. |
15 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
1
by
This commit was manufactured by cvs2svn to create branch |
16 |
|
17 |
"""Mixin class for MailList which handles administrative requests.
|
|
18 |
||
19 |
Two types of admin requests are currently supported: adding members to a
|
|
20 |
closed or semi-closed list, and moderated posts.
|
|
21 |
||
22 |
Pending subscriptions which are requiring a user's confirmation are handled
|
|
23 |
elsewhere.
|
|
24 |
"""
|
|
25 |
||
26 |
import os |
|
27 |
import time |
|
28 |
import errno |
|
29 |
import cPickle |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
30 |
import marshal |
1
by
This commit was manufactured by cvs2svn to create branch |
31 |
from cStringIO import StringIO |
32 |
||
33 |
import email |
|
34 |
from email.MIMEMessage import MIMEMessage |
|
35 |
from email.Generator import Generator |
|
36 |
from email.Utils import getaddresses |
|
37 |
||
38 |
from Mailman import mm_cfg |
|
39 |
from Mailman import Utils |
|
40 |
from Mailman import Message |
|
41 |
from Mailman import Errors |
|
42 |
from Mailman.UserDesc import UserDesc |
|
43 |
from Mailman.Queue.sbcache import get_switchboard |
|
44 |
from Mailman.Logging.Syslog import syslog |
|
45 |
from Mailman import i18n |
|
46 |
||
47 |
_ = i18n._ |
|
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
48 |
def D_(s): |
49 |
return s |
|
1
by
This commit was manufactured by cvs2svn to create branch |
50 |
|
51 |
# Request types requiring admin approval
|
|
52 |
IGN = 0 |
|
53 |
HELDMSG = 1 |
|
54 |
SUBSCRIPTION = 2 |
|
55 |
UNSUBSCRIPTION = 3 |
|
56 |
||
57 |
# Return status from __handlepost()
|
|
58 |
DEFER = 0 |
|
59 |
REMOVE = 1 |
|
60 |
LOST = 2 |
|
61 |
||
62 |
DASH = '-' |
|
63 |
NL = '\n' |
|
64 |
||
421
by bwarsaw
Add True/False compatibility for older Pythons. Closes SF bug #955381. |
65 |
try: |
66 |
True, False |
|
67 |
except NameError: |
|
68 |
True = 1 |
|
69 |
False = 0 |
|
70 |
||
1
by
This commit was manufactured by cvs2svn to create branch |
71 |
|
72 |
||
73 |
class ListAdmin: |
|
74 |
def InitVars(self): |
|
75 |
# non-configurable data
|
|
76 |
self.next_request_id = 1 |
|
77 |
||
78 |
def InitTempVars(self): |
|
79 |
self.__db = None |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
80 |
self.__filename = os.path.join(self.fullpath(), 'request.pck') |
1
by
This commit was manufactured by cvs2svn to create branch |
81 |
|
82 |
def __opendb(self): |
|
83 |
if self.__db is None: |
|
84 |
assert self.Locked() |
|
85 |
try: |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
86 |
fp = open(self.__filename) |
87 |
try: |
|
88 |
self.__db = cPickle.load(fp) |
|
89 |
finally: |
|
90 |
fp.close() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
91 |
except IOError, e: |
92 |
if e.errno <> errno.ENOENT: raise |
|
93 |
self.__db = {} |
|
446
by tkikuchi
SF patch: [ 970383 ] moderator -1 admin requests pending. |
94 |
# put version number in new database
|
95 |
self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION |
|
1
by
This commit was manufactured by cvs2svn to create branch |
96 |
|
97 |
def __closedb(self): |
|
98 |
if self.__db is not None: |
|
99 |
assert self.Locked() |
|
100 |
# Save the version number
|
|
101 |
self.__db['version'] = IGN, mm_cfg.REQUESTS_FILE_SCHEMA_VERSION |
|
102 |
# Now save a temp file and do the tmpfile->real file dance. BAW:
|
|
103 |
# should we be as paranoid as for the config.pck file? Should we
|
|
104 |
# use pickle?
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
105 |
tmpfile = self.__filename + '.tmp' |
1489
by Mark Sapiro
Mailman's log files, request.pck files and heldmsg-* files are no |
106 |
omask = os.umask(007) |
1
by
This commit was manufactured by cvs2svn to create branch |
107 |
try: |
108 |
fp = open(tmpfile, 'w') |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
109 |
try: |
110 |
cPickle.dump(self.__db, fp, 1) |
|
111 |
fp.flush() |
|
112 |
os.fsync(fp.fileno()) |
|
113 |
finally: |
|
114 |
fp.close() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
115 |
finally: |
116 |
os.umask(omask) |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
117 |
self.__db = None |
1
by
This commit was manufactured by cvs2svn to create branch |
118 |
# Do the dance
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
119 |
os.rename(tmpfile, self.__filename) |
1
by
This commit was manufactured by cvs2svn to create branch |
120 |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
121 |
def __nextid(self): |
122 |
assert self.Locked() |
|
123 |
while True: |
|
124 |
next = self.next_request_id |
|
125 |
self.next_request_id += 1 |
|
126 |
if not self.__db.has_key(next): |
|
127 |
break
|
|
128 |
return next |
|
1
by
This commit was manufactured by cvs2svn to create branch |
129 |
|
130 |
def SaveRequestsDb(self): |
|
131 |
self.__closedb() |
|
132 |
||
133 |
def NumRequestsPending(self): |
|
134 |
self.__opendb() |
|
446
by tkikuchi
SF patch: [ 970383 ] moderator -1 admin requests pending. |
135 |
# Subtract one for the version pseudo-entry
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
136 |
return len(self.__db) - 1 |
1
by
This commit was manufactured by cvs2svn to create branch |
137 |
|
138 |
def __getmsgids(self, rtype): |
|
139 |
self.__opendb() |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
140 |
ids = [k for k, (op, data) in self.__db.items() if op == rtype] |
1
by
This commit was manufactured by cvs2svn to create branch |
141 |
ids.sort() |
142 |
return ids |
|
143 |
||
144 |
def GetHeldMessageIds(self): |
|
145 |
return self.__getmsgids(HELDMSG) |
|
146 |
||
147 |
def GetSubscriptionIds(self): |
|
148 |
return self.__getmsgids(SUBSCRIPTION) |
|
149 |
||
150 |
def GetUnsubscriptionIds(self): |
|
151 |
return self.__getmsgids(UNSUBSCRIPTION) |
|
152 |
||
153 |
def GetRecord(self, id): |
|
154 |
self.__opendb() |
|
155 |
type, data = self.__db[id] |
|
156 |
return data |
|
157 |
||
158 |
def GetRecordType(self, id): |
|
159 |
self.__opendb() |
|
160 |
type, data = self.__db[id] |
|
161 |
return type |
|
162 |
||
163 |
def HandleRequest(self, id, value, comment=None, preserve=None, |
|
164 |
forward=None, addr=None): |
|
165 |
self.__opendb() |
|
166 |
rtype, data = self.__db[id] |
|
167 |
if rtype == HELDMSG: |
|
168 |
status = self.__handlepost(data, value, comment, preserve, |
|
169 |
forward, addr) |
|
170 |
elif rtype == UNSUBSCRIPTION: |
|
171 |
status = self.__handleunsubscription(data, value, comment) |
|
172 |
else: |
|
173 |
assert rtype == SUBSCRIPTION |
|
174 |
status = self.__handlesubscription(data, value, comment) |
|
175 |
if status <> DEFER: |
|
176 |
# BAW: Held message ids are linked to Pending cookies, allowing
|
|
177 |
# the user to cancel their post before the moderator has approved
|
|
178 |
# it. We should probably remove the cookie associated with this
|
|
179 |
# id, but we have no way currently of correlating them. :(
|
|
180 |
del self.__db[id] |
|
181 |
||
182 |
def HoldMessage(self, msg, reason, msgdata={}): |
|
183 |
# Make a copy of msgdata so that subsequent changes won't corrupt the
|
|
184 |
# request database. TBD: remove the `filebase' key since this will
|
|
185 |
# not be relevant when the message is resurrected.
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
186 |
msgdata = msgdata.copy() |
1
by
This commit was manufactured by cvs2svn to create branch |
187 |
# assure that the database is open for writing
|
188 |
self.__opendb() |
|
189 |
# get the next unique id
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
190 |
id = self.__nextid() |
1
by
This commit was manufactured by cvs2svn to create branch |
191 |
# get the message sender
|
192 |
sender = msg.get_sender() |
|
193 |
# calculate the file name for the message text and write it to disk
|
|
194 |
if mm_cfg.HOLD_MESSAGES_AS_PICKLES: |
|
195 |
ext = 'pck' |
|
196 |
else: |
|
197 |
ext = 'txt' |
|
198 |
filename = 'heldmsg-%s-%d.%s' % (self.internal_name(), id, ext) |
|
1489
by Mark Sapiro
Mailman's log files, request.pck files and heldmsg-* files are no |
199 |
omask = os.umask(007) |
1
by
This commit was manufactured by cvs2svn to create branch |
200 |
try: |
201 |
fp = open(os.path.join(mm_cfg.DATA_DIR, filename), 'w') |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
202 |
try: |
203 |
if mm_cfg.HOLD_MESSAGES_AS_PICKLES: |
|
204 |
cPickle.dump(msg, fp, 1) |
|
205 |
else: |
|
206 |
g = Generator(fp) |
|
1183
by Mark Sapiro
Another Python 2.6 compatibility change. |
207 |
g.flatten(msg, 1) |
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
208 |
fp.flush() |
209 |
os.fsync(fp.fileno()) |
|
210 |
finally: |
|
211 |
fp.close() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
212 |
finally: |
213 |
os.umask(omask) |
|
214 |
# save the information to the request database. for held message
|
|
215 |
# entries, each record in the database will be of the following
|
|
216 |
# format:
|
|
217 |
#
|
|
218 |
# the time the message was received
|
|
219 |
# the sender of the message
|
|
220 |
# the message's subject
|
|
221 |
# a string description of the problem
|
|
222 |
# name of the file in $PREFIX/data containing the msg text
|
|
223 |
# an additional dictionary of message metadata
|
|
224 |
#
|
|
225 |
msgsubject = msg.get('subject', _('(no subject)')) |
|
1333
by Mark Sapiro
A held message with a null sender caused a crash in the admindb |
226 |
if not sender: |
227 |
sender = _('<missing>') |
|
1
by
This commit was manufactured by cvs2svn to create branch |
228 |
data = time.time(), sender, msgsubject, reason, filename, msgdata |
229 |
self.__db[id] = (HELDMSG, data) |
|
230 |
return id |
|
231 |
||
232 |
def __handlepost(self, record, value, comment, preserve, forward, addr): |
|
233 |
# For backwards compatibility with pre 2.0beta3
|
|
234 |
ptime, sender, subject, reason, filename, msgdata = record |
|
235 |
path = os.path.join(mm_cfg.DATA_DIR, filename) |
|
236 |
# Handle message preservation
|
|
237 |
if preserve: |
|
238 |
parts = os.path.split(path)[1].split(DASH) |
|
239 |
parts[0] = 'spam' |
|
240 |
spamfile = DASH.join(parts) |
|
241 |
# Preserve the message as plain text, not as a pickle
|
|
242 |
try: |
|
243 |
fp = open(path) |
|
244 |
except IOError, e: |
|
245 |
if e.errno <> errno.ENOENT: raise |
|
246 |
return LOST |
|
247 |
try: |
|
1448
by Mark Sapiro
- Fixed a bug in ListAdmin._handlepost that would crash when trying to |
248 |
if path.endswith('.pck'): |
249 |
msg = cPickle.load(fp) |
|
250 |
else: |
|
251 |
assert path.endswith('.txt'), '%s not .pck or .txt' % path |
|
252 |
msg = fp.read() |
|
1
by
This commit was manufactured by cvs2svn to create branch |
253 |
finally: |
254 |
fp.close() |
|
255 |
# Save the plain text to a .msg file, not a .pck file
|
|
256 |
outpath = os.path.join(mm_cfg.SPAM_DIR, spamfile) |
|
257 |
head, ext = os.path.splitext(outpath) |
|
258 |
outpath = head + '.msg' |
|
259 |
outfp = open(outpath, 'w') |
|
260 |
try: |
|
1448
by Mark Sapiro
- Fixed a bug in ListAdmin._handlepost that would crash when trying to |
261 |
if path.endswith('.pck'): |
262 |
g = Generator(outfp) |
|
263 |
g.flatten(msg, 1) |
|
264 |
else: |
|
265 |
outfp.write(msg) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
266 |
finally: |
267 |
outfp.close() |
|
268 |
# Now handle updates to the database
|
|
269 |
rejection = None |
|
270 |
fp = None |
|
271 |
msg = None |
|
272 |
status = REMOVE |
|
273 |
if value == mm_cfg.DEFER: |
|
274 |
# Defer
|
|
275 |
status = DEFER |
|
276 |
elif value == mm_cfg.APPROVE: |
|
277 |
# Approved.
|
|
278 |
try: |
|
279 |
msg = readMessage(path) |
|
280 |
except IOError, e: |
|
281 |
if e.errno <> errno.ENOENT: raise |
|
282 |
return LOST |
|
283 |
msg = readMessage(path) |
|
284 |
msgdata['approved'] = 1 |
|
285 |
# adminapproved is used by the Emergency handler
|
|
286 |
msgdata['adminapproved'] = 1 |
|
287 |
# Calculate a new filebase for the approved message, otherwise
|
|
288 |
# delivery errors will cause duplicates.
|
|
289 |
try: |
|
290 |
del msgdata['filebase'] |
|
291 |
except KeyError: |
|
292 |
pass
|
|
293 |
# Queue the file for delivery by qrunner. Trying to deliver the
|
|
294 |
# message directly here can lead to a huge delay in web
|
|
295 |
# turnaround. Log the moderation and add a header.
|
|
296 |
msg['X-Mailman-Approved-At'] = email.Utils.formatdate(localtime=1) |
|
1451
by Mark Sapiro
- Added the list name to the vette log "held message approved" entry. |
297 |
syslog('vette', '%s: held message approved, message-id: %s', |
298 |
self.internal_name(), |
|
1
by
This commit was manufactured by cvs2svn to create branch |
299 |
msg.get('message-id', 'n/a')) |
300 |
# Stick the message back in the incoming queue for further
|
|
301 |
# processing.
|
|
302 |
inq = get_switchboard(mm_cfg.INQUEUE_DIR) |
|
303 |
inq.enqueue(msg, _metadata=msgdata) |
|
304 |
elif value == mm_cfg.REJECT: |
|
305 |
# Rejected
|
|
306 |
rejection = 'Refused' |
|
1204
by Mark Sapiro
Decoded RFC 2047 encoded message subjects for a few reports. Bug #266428. |
307 |
lang = self.getMemberLanguage(sender) |
308 |
subject = Utils.oneline(subject, Utils.GetCharSet(lang)) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
309 |
self.__refuse(_('Posting of your message titled "%(subject)s"'), |
310 |
sender, comment or _('[No reason given]'), |
|
1204
by Mark Sapiro
Decoded RFC 2047 encoded message subjects for a few reports. Bug #266428. |
311 |
lang=lang) |
1
by
This commit was manufactured by cvs2svn to create branch |
312 |
else: |
313 |
assert value == mm_cfg.DISCARD |
|
314 |
# Discarded
|
|
315 |
rejection = 'Discarded' |
|
316 |
# Forward the message
|
|
317 |
if forward and addr: |
|
318 |
# If we've approved the message, we need to be sure to craft a
|
|
319 |
# completely unique second message for the forwarding operation,
|
|
320 |
# since we don't want to share any state or information with the
|
|
321 |
# normal delivery.
|
|
322 |
try: |
|
323 |
copy = readMessage(path) |
|
324 |
except IOError, e: |
|
325 |
if e.errno <> errno.ENOENT: raise |
|
326 |
raise Errors.LostHeldMessage(path) |
|
327 |
# It's possible the addr is a comma separated list of addresses.
|
|
328 |
addrs = getaddresses([addr]) |
|
329 |
if len(addrs) == 1: |
|
330 |
realname, addr = addrs[0] |
|
331 |
# If the address getting the forwarded message is a member of
|
|
332 |
# the list, we want the headers of the outer message to be
|
|
333 |
# encoded in their language. Otherwise it'll be the preferred
|
|
334 |
# language of the mailing list.
|
|
335 |
lang = self.getMemberLanguage(addr) |
|
336 |
else: |
|
337 |
# Throw away the realnames
|
|
338 |
addr = [a for realname, a in addrs] |
|
339 |
# Which member language do we attempt to use? We could use
|
|
340 |
# the first match or the first address, but in the face of
|
|
341 |
# ambiguity, let's just use the list's preferred language
|
|
342 |
lang = self.preferred_language |
|
343 |
otrans = i18n.get_translation() |
|
344 |
i18n.set_language(lang) |
|
345 |
try: |
|
346 |
fmsg = Message.UserNotification( |
|
347 |
addr, self.GetBouncesEmail(), |
|
348 |
_('Forward of moderated message'), |
|
349 |
lang=lang) |
|
350 |
finally: |
|
351 |
i18n.set_translation(otrans) |
|
352 |
fmsg.set_type('message/rfc822') |
|
353 |
fmsg.attach(copy) |
|
354 |
fmsg.send(self) |
|
355 |
# Log the rejection
|
|
20
by bwarsaw
Backporting from the trunk. |
356 |
if rejection: |
1
by
This commit was manufactured by cvs2svn to create branch |
357 |
note = '''%(listname)s: %(rejection)s posting: |
358 |
\tFrom: %(sender)s |
|
359 |
\tSubject: %(subject)s''' % { |
|
360 |
'listname' : self.internal_name(), |
|
361 |
'rejection': rejection, |
|
352
by bwarsaw
__handlepost(): sender or subject could be Header instances, so str-ify them |
362 |
'sender' : str(sender).replace('%', '%%'), |
363 |
'subject' : str(subject).replace('%', '%%'), |
|
1
by
This commit was manufactured by cvs2svn to create branch |
364 |
}
|
365 |
if comment: |
|
366 |
note += '\n\tReason: ' + comment.replace('%', '%%') |
|
367 |
syslog('vette', note) |
|
368 |
# Always unlink the file containing the message text. It's not
|
|
369 |
# necessary anymore, regardless of the disposition of the message.
|
|
370 |
if status <> DEFER: |
|
371 |
try: |
|
372 |
os.unlink(path) |
|
373 |
except OSError, e: |
|
374 |
if e.errno <> errno.ENOENT: raise |
|
375 |
# We lost the message text file. Clean up our housekeeping
|
|
376 |
# and inform of this status.
|
|
377 |
return LOST |
|
378 |
return status |
|
20
by bwarsaw
Backporting from the trunk. |
379 |
|
1
by
This commit was manufactured by cvs2svn to create branch |
380 |
def HoldSubscription(self, addr, fullname, password, digest, lang): |
381 |
# Assure that the database is open for writing
|
|
382 |
self.__opendb() |
|
383 |
# Get the next unique id
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
384 |
id = self.__nextid() |
1
by
This commit was manufactured by cvs2svn to create branch |
385 |
# Save the information to the request database. for held subscription
|
386 |
# entries, each record in the database will be one of the following
|
|
387 |
# format:
|
|
388 |
#
|
|
389 |
# the time the subscription request was received
|
|
390 |
# the subscriber's address
|
|
391 |
# the subscriber's selected password (TBD: is this safe???)
|
|
392 |
# the digest flag
|
|
20
by bwarsaw
Backporting from the trunk. |
393 |
# the user's preferred language
|
1
by
This commit was manufactured by cvs2svn to create branch |
394 |
data = time.time(), addr, fullname, password, digest, lang |
395 |
self.__db[id] = (SUBSCRIPTION, data) |
|
396 |
#
|
|
397 |
# TBD: this really shouldn't go here but I'm not sure where else is
|
|
398 |
# appropriate.
|
|
399 |
syslog('vette', '%s: held subscription request from %s', |
|
400 |
self.internal_name(), addr) |
|
401 |
# Possibly notify the administrator in default list language
|
|
402 |
if self.admin_immed_notify: |
|
1353
by Mark Sapiro
Fixed a bug that could send an admin notice of a held subscription with |
403 |
i18n.set_language(self.preferred_language) |
1
by
This commit was manufactured by cvs2svn to create branch |
404 |
realname = self.real_name |
405 |
subject = _( |
|
406 |
'New subscription request to list %(realname)s from %(addr)s') |
|
407 |
text = Utils.maketext( |
|
408 |
'subauth.txt', |
|
409 |
{'username' : addr, |
|
410 |
'listname' : self.internal_name(), |
|
411 |
'hostname' : self.host_name, |
|
412 |
'admindb_url': self.GetScriptURL('admindb', absolute=1), |
|
413 |
}, mlist=self) |
|
414 |
# This message should appear to come from the <list>-owner so as
|
|
415 |
# to avoid any useless bounce processing.
|
|
416 |
owneraddr = self.GetOwnerEmail() |
|
417 |
msg = Message.UserNotification(owneraddr, owneraddr, subject, text, |
|
418 |
self.preferred_language) |
|
419 |
msg.send(self, **{'tomoderators': 1}) |
|
1353
by Mark Sapiro
Fixed a bug that could send an admin notice of a held subscription with |
420 |
# Restore the user's preferred language.
|
421 |
i18n.set_language(lang) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
422 |
|
423 |
def __handlesubscription(self, record, value, comment): |
|
1780
by Mark Sapiro
Added global _ where needed. |
424 |
global _ |
1
by
This commit was manufactured by cvs2svn to create branch |
425 |
stime, addr, fullname, password, digest, lang = record |
426 |
if value == mm_cfg.DEFER: |
|
427 |
return DEFER |
|
428 |
elif value == mm_cfg.DISCARD: |
|
1205
by Mark Sapiro
- Added vette logging for rejected and discarded (un)subscribe requests. |
429 |
syslog('vette', '%s: discarded subscription request from %s', |
430 |
self.internal_name(), addr) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
431 |
elif value == mm_cfg.REJECT: |
432 |
self.__refuse(_('Subscription request'), addr, |
|
433 |
comment or _('[No reason given]'), |
|
434 |
lang=lang) |
|
1205
by Mark Sapiro
- Added vette logging for rejected and discarded (un)subscribe requests. |
435 |
syslog('vette', """%s: rejected subscription request from %s |
436 |
\tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') |
|
1
by
This commit was manufactured by cvs2svn to create branch |
437 |
else: |
438 |
# subscribe
|
|
439 |
assert value == mm_cfg.SUBSCRIBE |
|
440 |
try: |
|
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
441 |
_ = D_ |
442 |
whence = _('via admin approval') |
|
443 |
_ = i18n._ |
|
1
by
This commit was manufactured by cvs2svn to create branch |
444 |
userdesc = UserDesc(addr, fullname, password, digest, lang) |
1776
by Mark Sapiro
I18n for new whence reasons in admin (un)subscribe notices. |
445 |
self.ApprovedAddMember(userdesc, whence=whence) |
1
by
This commit was manufactured by cvs2svn to create branch |
446 |
except Errors.MMAlreadyAMember: |
447 |
# User has already been subscribed, after sending the request
|
|
448 |
pass
|
|
449 |
# TBD: disgusting hack: ApprovedAddMember() can end up closing
|
|
450 |
# the request database.
|
|
451 |
self.__opendb() |
|
452 |
return REMOVE |
|
453 |
||
454 |
def HoldUnsubscription(self, addr): |
|
455 |
# Assure the database is open for writing
|
|
456 |
self.__opendb() |
|
457 |
# Get the next unique id
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
458 |
id = self.__nextid() |
1
by
This commit was manufactured by cvs2svn to create branch |
459 |
# All we need to do is save the unsubscribing address
|
460 |
self.__db[id] = (UNSUBSCRIPTION, addr) |
|
461 |
syslog('vette', '%s: held unsubscription request from %s', |
|
462 |
self.internal_name(), addr) |
|
463 |
# Possibly notify the administrator of the hold
|
|
464 |
if self.admin_immed_notify: |
|
465 |
realname = self.real_name |
|
466 |
subject = _( |
|
467 |
'New unsubscription request from %(realname)s by %(addr)s') |
|
468 |
text = Utils.maketext( |
|
469 |
'unsubauth.txt', |
|
470 |
{'username' : addr, |
|
471 |
'listname' : self.internal_name(), |
|
472 |
'hostname' : self.host_name, |
|
473 |
'admindb_url': self.GetScriptURL('admindb', absolute=1), |
|
474 |
}, mlist=self) |
|
475 |
# This message should appear to come from the <list>-owner so as
|
|
476 |
# to avoid any useless bounce processing.
|
|
477 |
owneraddr = self.GetOwnerEmail() |
|
478 |
msg = Message.UserNotification(owneraddr, owneraddr, subject, text, |
|
479 |
self.preferred_language) |
|
480 |
msg.send(self, **{'tomoderators': 1}) |
|
481 |
||
482 |
def __handleunsubscription(self, record, value, comment): |
|
483 |
addr = record |
|
484 |
if value == mm_cfg.DEFER: |
|
485 |
return DEFER |
|
486 |
elif value == mm_cfg.DISCARD: |
|
1205
by Mark Sapiro
- Added vette logging for rejected and discarded (un)subscribe requests. |
487 |
syslog('vette', '%s: discarded unsubscription request from %s', |
488 |
self.internal_name(), addr) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
489 |
elif value == mm_cfg.REJECT: |
490 |
self.__refuse(_('Unsubscription request'), addr, comment) |
|
1205
by Mark Sapiro
- Added vette logging for rejected and discarded (un)subscribe requests. |
491 |
syslog('vette', """%s: rejected unsubscription request from %s |
492 |
\tReason: %s""", self.internal_name(), addr, comment or '[No reason given]') |
|
1
by
This commit was manufactured by cvs2svn to create branch |
493 |
else: |
494 |
assert value == mm_cfg.UNSUBSCRIBE |
|
495 |
try: |
|
496 |
self.ApprovedDeleteMember(addr) |
|
497 |
except Errors.NotAMemberError: |
|
498 |
# User has already been unsubscribed
|
|
499 |
pass
|
|
500 |
return REMOVE |
|
501 |
||
502 |
def __refuse(self, request, recip, comment, origmsg=None, lang=None): |
|
503 |
# As this message is going to the requestor, try to set the language
|
|
504 |
# to his/her language choice, if they are a member. Otherwise use the
|
|
505 |
# list's preferred language.
|
|
506 |
realname = self.real_name |
|
20
by bwarsaw
Backporting from the trunk. |
507 |
if lang is None: |
1
by
This commit was manufactured by cvs2svn to create branch |
508 |
lang = self.getMemberLanguage(recip) |
509 |
text = Utils.maketext( |
|
510 |
'refuse.txt', |
|
511 |
{'listname' : realname, |
|
512 |
'request' : request, |
|
513 |
'reason' : comment, |
|
514 |
'adminaddr': self.GetOwnerEmail(), |
|
515 |
}, lang=lang, mlist=self) |
|
516 |
otrans = i18n.get_translation() |
|
517 |
i18n.set_language(lang) |
|
518 |
try: |
|
519 |
# add in original message, but not wrap/filled
|
|
520 |
if origmsg: |
|
521 |
text = NL.join( |
|
522 |
[text, |
|
523 |
'---------- ' + _('Original Message') + ' ----------', |
|
524 |
str(origmsg) |
|
525 |
])
|
|
526 |
subject = _('Request to mailing list %(realname)s rejected') |
|
527 |
finally: |
|
528 |
i18n.set_translation(otrans) |
|
1122
by Mark Sapiro
Changed ListAdmin.py to make rejected post messages From: the -owner address |
529 |
msg = Message.UserNotification(recip, self.GetOwnerEmail(), |
1
by
This commit was manufactured by cvs2svn to create branch |
530 |
subject, text, lang) |
531 |
msg.send(self) |
|
532 |
||
533 |
def _UpdateRecords(self): |
|
534 |
# Subscription records have changed since MM2.0.x. In that family,
|
|
535 |
# the records were of length 4, containing the request time, the
|
|
536 |
# address, the password, and the digest flag. In MM2.1a2, they grew
|
|
537 |
# an additional language parameter at the end. In MM2.1a4, they grew
|
|
538 |
# a fullname slot after the address. This semi-public method is used
|
|
539 |
# by the update script to coerce all subscription records to the
|
|
540 |
# latest MM2.1 format.
|
|
541 |
#
|
|
542 |
# Held message records have historically either 5 or 6 items too.
|
|
543 |
# These always include the requests time, the sender, subject, default
|
|
544 |
# rejection reason, and message text. When of length 6, it also
|
|
545 |
# includes the message metadata dictionary on the end of the tuple.
|
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
546 |
#
|
547 |
# In Mailman 2.1.5 we converted these files to pickles.
|
|
548 |
filename = os.path.join(self.fullpath(), 'request.db') |
|
549 |
try: |
|
550 |
fp = open(filename) |
|
551 |
try: |
|
552 |
self.__db = marshal.load(fp) |
|
553 |
finally: |
|
554 |
fp.close() |
|
555 |
os.unlink(filename) |
|
556 |
except IOError, e: |
|
557 |
if e.errno <> errno.ENOENT: raise |
|
558 |
filename = os.path.join(self.fullpath(), 'request.pck') |
|
559 |
try: |
|
560 |
fp = open(filename) |
|
561 |
try: |
|
562 |
self.__db = cPickle.load(fp) |
|
563 |
finally: |
|
564 |
fp.close() |
|
565 |
except IOError, e: |
|
566 |
if e.errno <> errno.ENOENT: raise |
|
567 |
self.__db = {} |
|
1127
by Mark Sapiro
The immediately preceding fix for bug #266106 (sf998384) was incomplete. |
568 |
for id, x in self.__db.items(): |
569 |
# A bug in versions 2.1.1 through 2.1.11 could have resulted in
|
|
570 |
# just info being stored instead of (op, info)
|
|
571 |
if len(x) == 2: |
|
572 |
op, info = x |
|
573 |
elif len(x) == 6: |
|
574 |
# This is the buggy info. Check for digest flag.
|
|
575 |
if x[4] in (0, 1): |
|
576 |
op = SUBSCRIPTION |
|
577 |
else: |
|
578 |
op = HELDMSG |
|
579 |
self.__db[id] = op, x |
|
580 |
continue
|
|
581 |
else: |
|
582 |
assert False, 'Unknown record format in %s' % self.__filename |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
583 |
if op == SUBSCRIPTION: |
1
by
This commit was manufactured by cvs2svn to create branch |
584 |
if len(info) == 4: |
585 |
# pre-2.1a2 compatibility
|
|
586 |
when, addr, passwd, digest = info |
|
587 |
fullname = '' |
|
588 |
lang = self.preferred_language |
|
589 |
elif len(info) == 5: |
|
590 |
# pre-2.1a4 compatibility
|
|
591 |
when, addr, passwd, digest, lang = info |
|
592 |
fullname = '' |
|
593 |
else: |
|
594 |
assert len(info) == 6, 'Unknown subscription record layout' |
|
595 |
continue
|
|
596 |
# Here's the new layout
|
|
1126
by Mark Sapiro
Since Mailman 2.1.1, 2.0.x outstanding subscription and held message |
597 |
self.__db[id] = op, (when, addr, fullname, passwd, |
598 |
digest, lang) |
|
329
by bwarsaw
Rewritten, simplified, and made more bullet-proof. The file read/written is |
599 |
elif op == HELDMSG: |
1
by
This commit was manufactured by cvs2svn to create branch |
600 |
if len(info) == 5: |
601 |
when, sender, subject, reason, text = info |
|
602 |
msgdata = {} |
|
603 |
else: |
|
604 |
assert len(info) == 6, 'Unknown held msg record layout' |
|
605 |
continue
|
|
606 |
# Here's the new layout
|
|
1126
by Mark Sapiro
Since Mailman 2.1.1, 2.0.x outstanding subscription and held message |
607 |
self.__db[id] = op, (when, sender, subject, reason, |
608 |
text, msgdata) |
|
1
by
This commit was manufactured by cvs2svn to create branch |
609 |
# All done
|
610 |
self.__closedb() |
|
611 |
||
612 |
||
613 |
||
614 |
def readMessage(path): |
|
615 |
# For backwards compatibility, we must be able to read either a flat text
|
|
616 |
# file or a pickle.
|
|
617 |
ext = os.path.splitext(path)[1] |
|
618 |
fp = open(path) |
|
619 |
try: |
|
620 |
if ext == '.txt': |
|
621 |
msg = email.message_from_file(fp, Message.Message) |
|
622 |
else: |
|
623 |
assert ext == '.pck' |
|
624 |
msg = cPickle.load(fp) |
|
625 |
finally: |
|
626 |
fp.close() |
|
627 |
return msg |