Skip to content

Commit b6b934e

Browse files
committed
update
1 parent a8023b2 commit b6b934e

1 file changed

Lines changed: 117 additions & 68 deletions

File tree

Mailman/Queue/CommandRunner.py

Lines changed: 117 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -351,73 +351,122 @@ def _validate_message(self, msg, msgdata):
351351
return msg, False
352352

353353
def _dispose(self, mlist, msg, msgdata):
354-
# Validate message type first
355-
msg, success = self._validate_message(msg, msgdata)
356-
if not success:
357-
mailman_log('error', 'Message validation failed for command message')
358-
return False
359-
360-
# Get the list name from the mlist object
354+
"""Process a command message.
355+
356+
Args:
357+
mlist: The MailList instance this message is destined for
358+
msg: The Message object representing the message
359+
msgdata: Dictionary of message metadata
360+
361+
Returns:
362+
bool: True if message should be requeued, False if processing is complete
363+
"""
364+
msgid = msg.get('message-id', 'n/a')
365+
filebase = msgdata.get('_filebase', 'unknown')
366+
367+
# Ensure we have a MailList object
368+
if isinstance(mlist, str):
369+
try:
370+
mlist = get_maillist()(mlist, lock=0)
371+
should_unlock = True
372+
except Errors.MMUnknownListError:
373+
syslog('error', 'CommandRunner: Unknown list %s', mlist)
374+
self._shunt.enqueue(msg, msgdata)
375+
return False
376+
else:
377+
should_unlock = False
378+
361379
try:
362-
listname = mlist.internal_name() if hasattr(mlist, 'internal_name') else str(mlist)
363-
MailList = get_maillist()
364-
mlist_obj = MailList(listname, lock=False)
365-
except Errors.MMListError as e:
366-
mailman_log('error', 'Failed to get MailList object for %s: %s', listname, str(e))
367-
return False
368-
369-
# The policy here is similar to the Replybot policy. If a message has
370-
# "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard
371-
# it to prevent replybot response storms.
372-
precedence = msg.get('precedence', '').lower()
373-
ack = msg.get('x-ack', '').lower()
374-
if ack != 'yes' and precedence in ('bulk', 'junk', 'list'):
375-
syslog('vette', 'Precedence: %s message discarded by: %s',
376-
precedence, mlist_obj.GetRequestEmail())
377-
return False
380+
syslog('debug', 'CommandRunner._dispose: Starting to process command message %s (file: %s) for list %s',
381+
msgid, filebase, mlist.internal_name())
382+
383+
# Check retry delay and duplicate processing
384+
if not self._check_retry_delay(msgid, filebase):
385+
syslog('debug', 'CommandRunner._dispose: Message %s failed retry delay check, skipping', msgid)
386+
return True
378387

379-
# Lock the list before any operations
380-
try:
381-
mlist_obj.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
382-
except LockFile.TimeOutError:
383-
# Oh well, try again later
384-
return True
388+
# Validate message type first
389+
msg, success = self._validate_message(msg, msgdata)
390+
if not success:
391+
syslog('error', 'CommandRunner._dispose: Message validation failed for message %s', msgid)
392+
msgdata['_validation_failure'] = 'Missing required headers'
393+
self._shunt.enqueue(msg, msgdata)
394+
return False
385395

386-
try:
387-
# Do replybot for commands
388-
mlist_obj.Load()
389-
Replybot = get_replybot()
390-
Replybot.process(mlist_obj, msg, msgdata)
391-
if mlist_obj.autorespond_requests == 1:
392-
syslog('vette', 'replied and discard')
393-
# w/discard
396+
# The policy here is similar to the Replybot policy. If a message has
397+
# "Precedence: bulk|junk|list" and no "X-Ack: yes" header, we discard
398+
# it to prevent replybot response storms.
399+
precedence = msg.get('precedence', '').lower()
400+
ack = msg.get('x-ack', '').lower()
401+
if ack != 'yes' and precedence in ('bulk', 'junk', 'list'):
402+
syslog('vette', 'Precedence: %s message discarded by: %s',
403+
precedence, mlist.GetRequestEmail())
394404
return False
395405

396-
# Now craft the response
397-
res = Results(mlist_obj, msg, msgdata)
398-
# This message will have been delivered to one of mylist-request,
399-
# mylist-join, or mylist-leave, and the message metadata will contain
400-
# a key to which one was used.
401-
ret = BADCMD
402-
if msgdata.get('torequest', False):
403-
ret = res.process()
404-
elif msgdata.get('tojoin', False):
405-
ret = res.do_command('join')
406-
elif msgdata.get('toleave', False):
407-
ret = res.do_command('leave')
408-
elif msgdata.get('toconfirm', False):
409-
mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE)
410-
if mo:
411-
ret = res.do_command('confirm', (mo.group('cookie'),))
412-
if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND:
413-
syslog('vette',
414-
'No command, message discarded, msgid: %s',
415-
msg.get('message-id', 'n/a'))
416-
else:
417-
res.send_response()
418-
mlist_obj.Save()
406+
# Lock the list before any operations
407+
try:
408+
mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
409+
except LockFile.TimeOutError:
410+
# Oh well, try again later
411+
return True
412+
413+
try:
414+
# Check if list is temporarily unavailable
415+
try:
416+
mlist.Load()
417+
except Errors.MMCorruptListDatabaseError as e:
418+
syslog('error', 'CommandRunner._dispose: List %s is temporarily unavailable: %s',
419+
mlist.internal_name(), str(e))
420+
return True
421+
except Exception as e:
422+
syslog('error', 'CommandRunner._dispose: Error loading list %s: %s',
423+
mlist.internal_name(), str(e))
424+
return True
425+
426+
# Do replybot for commands
427+
Replybot = get_replybot()
428+
Replybot.process(mlist, msg, msgdata)
429+
if mlist.autorespond_requests == 1:
430+
syslog('vette', 'replied and discard')
431+
# w/discard
432+
return False
433+
434+
# Now craft the response
435+
res = Results(mlist, msg, msgdata)
436+
# This message will have been delivered to one of mylist-request,
437+
# mylist-join, or mylist-leave, and the message metadata will contain
438+
# a key to which one was used.
439+
ret = BADCMD
440+
if msgdata.get('torequest', False):
441+
ret = res.process()
442+
elif msgdata.get('tojoin', False):
443+
ret = res.do_command('join')
444+
elif msgdata.get('toleave', False):
445+
ret = res.do_command('leave')
446+
elif msgdata.get('toconfirm', False):
447+
mo = re.match(mm_cfg.VERP_CONFIRM_REGEXP, msg.get('to', ''), re.IGNORECASE)
448+
if mo:
449+
ret = res.do_command('confirm', (mo.group('cookie'),))
450+
if ret == BADCMD and mm_cfg.DISCARD_MESSAGE_WITH_NO_COMMAND:
451+
syslog('vette',
452+
'No command, message discarded, msgid: %s',
453+
msg.get('message-id', 'n/a'))
454+
return False
455+
else:
456+
res.send_response()
457+
mlist.Save()
458+
return False
459+
finally:
460+
mlist.Unlock()
461+
462+
except Exception as e:
463+
syslog('error', 'CommandRunner._dispose: Error processing command message %s: %s\nTraceback:\n%s',
464+
msgid, str(e), traceback.format_exc())
465+
self._shunt.enqueue(msg, msgdata)
466+
return False
419467
finally:
420-
mlist_obj.Unlock()
468+
if should_unlock:
469+
mlist.Unlock()
421470

422471
def _oneloop(self):
423472
"""Process one batch of messages from the command queue."""
@@ -462,19 +511,19 @@ def _oneloop(self):
462511
self._shunt.enqueue(msg, msgdata)
463512
continue
464513

465-
# Validate message
466-
msg, success = self._validate_message(msg, msgdata)
467-
if not success:
468-
syslog('error', 'CommandRunner._oneloop: Message validation failed for %s', filebase)
469-
continue
470-
471514
# Process message
472515
try:
473-
self._dispose(mlist, msg, msgdata)
516+
success = self._dispose(mlist, msg, msgdata)
517+
if not success:
518+
# If _dispose returns False, the message was shunted or discarded
519+
# Remove it from the queue
520+
self._switchboard.finish(filebase)
474521
except Exception as e:
475522
syslog('error', 'CommandRunner._oneloop: Error processing message %s: %s',
476523
msg.get('message-id', 'n/a'), str(e))
477524
self._shunt.enqueue(msg, msgdata)
525+
# Remove the message from the queue after shunting
526+
self._switchboard.finish(filebase)
478527
except Exception as e:
479528
syslog('error', 'CommandRunner._oneloop: Error processing file %s: %s', filebase, str(e))
480529
except Exception as e:

0 commit comments

Comments
 (0)