# Copyright (C) 2022 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. # Prior to Mailman 3.3.5, some tokens for user confirmations were pended with # too long a lifetime. This script removes those pendings based on when they # were pended and the configured pending_request_life rather than their # expiration. # Also prior to Mailman 3.3.5, pended held_message tokens for email handling # of the message were not removed when the message was handled via REST. This # script removes those pendings too. # This is run with # mailman shell -r delete_orphans_expireds # after saving it as # /opt/mailman/mm/venv/bin/delete_orphans_expireds.py from datetime import datetime from lazr.config import as_timedelta from mailman.config import config from mailman.database.transaction import transactional from mailman.interfaces.pending import IPendings from zope.component import getUtility pendings = getUtility(IPendings) def is_request(id): if config.db.store.execute( 'SELECT * FROM _request WHERE id = {};'.format(id)).rowcount > 0: return True return False then = datetime.now() - as_timedelta(config.mailman.pending_request_life) thenm = datetime.now() - as_timedelta(config.mailman.moderator_request_life) def fromisoformat(x): if hasattr(datetime, 'fromisoformat'): return datetime.fromisoformat(x) try: return datetime.strptime(x, '%Y-%m-%dT%H:%M:%S.%f') except ValueError: return datetime.strptime(x, '%Y-%m-%dT%H:%M:%S') @transactional def delete_orphans_expireds(): count = 0 for token, data in pendings.find(pend_type='held message'): if data and not is_request(data['id']): result = pendings.confirm(token, expunge=True) count += 1 print(f'expunged {count} orphaned pended held messages') count = 0 for token, data in pendings.find(pend_type='data'): if data and data['_mod_hold_date']: when = data['_mod_hold_date'] if isinstance(when, str): when = fromisoformat(when) if when < thenm: result = pendings.confirm(token, expunge=True) count += 1 print(f'expunged {count} expired held messages') pends = list(pendings.find(pend_type='subscription')) pends += list(pendings.find(pend_type='unsubscription')) count = 0 for token, values in pends: if values and values['token_owner'] == 'subscriber': when = values['when'] if isinstance(when, str): when = fromisoformat(when) if when < then: result = pendings.confirm(token, expunge=True) count += 1 print(f'expunged {count} expired (un)subscription confirmations')